Будь умным!


У вас вопросы?
У нас ответы:) SamZan.net

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

Работа добавлена на сайт samzan.net: 2016-03-30

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

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

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

от 25%

Подписываем

договор

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

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

Перехват методов интерфейса 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<void**>(&pDispatch));

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

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

 reinterpret_cast<void**>(&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<void**>(pItf);

CComCritSecLock<CComAutoCriticalSection> 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<CComAutoCriticalSection> 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<IUnknown*>(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<IUnknown> spUnknown;

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

if (FAILED(hr))

return hr;

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

CredentialsHolder::CredentialsPtr* pCredentials = CredentialsFromItf(reinterpret_cast<IUnknown*>(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<void*>(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/




1. ВВВВВВВВВВВВ В Республике Казахстан по Конституции существует следующие формы собственности- Частная.
2. Введение в мировую журналистику
3. ДИХОТОМИЯ
4. increse of dditionl reserves by 9 The lrgest bnks of Europe were instructed to increse the mount of dditionl reserves by 9 in ll 106 billion euros
5. Экономическая характеристика классификация и методы измерения объемов услуг Услуга связи ~ это резул
6. Отличие культурологии от других наук, изучающих культуру
7. по теме Индексы По данным таблицы рассчитайте индексы себестоимости переменного и постоянного состава
8. История денег и банков
9. вариант 1 Для хранения растрового изображения размером 128~128 пикселей отвели 4 килобайта памяти
10. Курсовая работа- Учёт текущих обязательств и расчетов
11. информационная цивилизация или информационное общество
12. Medicte Signs of lcohol buse Progressive increses in the frequency nd quntity of lcohol consumption cn begin to produce more serious medicl symptoms of lcoholism
13. Реферат Гасанова Н
14. Тема 2 Оборотні фонди підприємства
15. Пока жива и здорова наша поэзия до тех пор нет причины сомневаться в глубоком здоровье русского народа
16. ГЕНЕЗИС 2004 УДК 159
17.  Теоретические аспекты проблемы жизненного цикла товара 5 1
18. Notenie Meno Tom~ Drusn Ro~n~k 1
19. Контрольная работа- Право спільної сумісної власності подружжя
20. Europen pplied Sciences ISSN 21952183 выдан Ntionles ISSNZentrum f~r Deutschlnd Национальный центр ISSN Германии Журнал зарегис