Сообщений 0    Оценка 75        Оценить  
Система Orphus

Known IUnknown

Как найти и восстановить недокументированные интерфейсы

Автор: Павел Блудов

Версия текста: 1.1

Просмотр интерфейсов
Реестр
Библиотечные (.lib и .obj) файлы
Отладочные файлы (.pdb)
Восстановление интерфейса
Использованные статьи и литература


Приложение IKnown test

Демонстрационное приложение (COM-объект + console) IKnown test (25kb)


Приложение PdbView

Демонстрационное приложение (WTL MDI) PdbView (26kb)

Просмотр интерфейсов

Одной из основ COM-программирования является интерфейс IUnknown. Метод QueryInterface() этого интерфейса позволяет получать все прочие интерфейсы этого объекта. К сожалению, это подразумевает, что вызывающая сторона отлично осведомлена о возможных интерфейсах объекта и даже знает все GUID'ы этих интерфейсов. А если нет? Если возникает необходимость просто просмотреть все интерфейсы, в научно-познавательных целях?

Тут есть два пути: узнать у разработчика и перебрать все возможные варианты GUID'ов интерфейсов. Увы, скорости нынешних процессоров недостаточно для того, чтобы перебрать все возможные 2 в 128 вариантов, и хорошо бы как-то уменьшить размер перебора. Давайте поищем большие скопления GUID'ов и отправим их всех в IUnknown::QueryInterface().

Реестр

Одно из таких скоплений это реестр Windows. Огромное количество интерфейсов прописано в HKEY_CLASSES_ROOT\Interface.

    CRegKey key;
    if(ERROR_SUCCESS == key.Open(HKEY_CLASSES_ROOT, _T("Interface")))
    {
        TCHAR szInterface[MAX_PATH];
        TCHAR szIID[64];

        for (DWORD dwIndex = 0;
            ERROR_SUCCESS == ::RegEnumKey(key, dwIndex, szIID, sizeof(szIID) / sizeof(szIID[0]));
              ++dwIndex)
        {
            LONG cb = MAX_PATH;

            if (ERROR_SUCCESS != ::RegQueryValue(key, szIID, szInterface, &cb))
                szInterface[0] = _T('\x0');
            // TODO
        }
    }

Такой способ использует DumpQIQS от Andrew Nosenko. Этот список дает приемлимые результаты, но, к сожалению, далеко не полон.

Библиотечные (.lib и .obj) файлы

Довольно большое количество GUID'ов недокументированных интерфейсов находится в .lib файлах uuid.lib, dxguid.lib, ADSIid.Lib и им подобным. Формат этих файлов хорошо известен и я не буду вдаваться в технические детали, скажу лишь, что для нас представляют интерес только объекты класса IMAGE_SYM_CLASS_EXTERNAL размером 16 байт, т.е. sizeof(GUID). Пример IKnown test содержит целиком код для разбора .lib файлов на .obj и поиск GUID'ов в .obj файлах.

Отладочные файлы (.pdb)

Гарантировано полный список всех интерфейсов, реализуемых объектами какого-либо модуля находится в его отладочном файле. Увы, раздобыть .pdb файл нужного компонента ничуть не проще, чем его исходный код. Проще все-же написать разработчику и прямо спросить: "А нету ли у вас интерфейса, реализующего то-то и то-то?".

Радостным исключением из этого правила явлвется Microsoft Windows NT. Полный набор .pdb файлов этой операционной системы (а она теперь включает Microsoft Internet Explorer и DirectX) доступен для бесплатной загрузки с Web-сервера Майкрософт под названием Windows Customer Support Diagnostics или через подписку MSDN. Стоит только заполучить в свои руки отладочный файл интересующиего нас модуля, как проблема перебора всех GUID'ов интерфейсов решается и решается на 100%.

Для этого нам нужно:

BOOL CALLBACK EnumSymbolsCallback(
            LPSTR szSymbolName,
            ULONG SymbolAddress,
            ULONG SymbolSize,
            PVOID pUserContext
            )
{
    // Обычно, GUID'ы объявляют как extern "C" так что их имена должны начинаться с '_'.
    if ('_' == szSymbolName[0] &&
        !::IsBadReadPtr(LPVOID(SymbolAddress), sizeof(GUID)))
    {
        IID iid = GUID_NULL;
        ::CopyMemory(&iid, LPVOID(SymbolAddress), sizeof(GUID));
        // TODO
    }

    return TRUE;
}
::SymEnumerateSymbols(m_hProcess, dwModuleBase, EnumSymbolsCallback, NULL);
Параметр SymbolSize, передаваемый в EnumSymbolsCallback не является действительным размером объекта. На самом деле, это просто разница адресов этого и следующего отладочных символов. Как правило, она совпадает с размером объекта, но бывает что и нет.

Объект IKnown из тестового приложения реализует все три способа. Пример использования:

    pKnown->LoadInterfacesFromRegistry();
    pKnown->LoadInterfacesFromLibrary(OLESTR("Uuid.Lib"));

    // Загружаем символы для модуля, в котором находится реализация
    // метода QueryInterface этого объекта
    DWORD_PTR *pVTable = ((DWORD_PTR **)pUnk.p)[0];

    pKnown->LoadInterfacesFromDebugSymbols(
        pKnown->GetPointerHModule(LPVOID(pVTable[0])));

    pKnown->DumpUnknown(pUnk, PrintCallback, LPVOID(pKnown));

Восстановление интерфейса

Ну хорошо, выяснили мы, какие интерфейсы есть у объекта. А толку-то? Что теперь делать с этим IID_OurInterfaceForInternalUseOnly? Где можно узнать про этот пресловутый IOurInterfaceForInternalUseOnly? Да все там же! В тех самых файлах с отладочной информацией. Чтобы понять, как это возможно, нужно сначала разобраться, что же такое COM-интерфейс и как его реализуют нынешние компиляторы.

Итак, COM-интерфейс это абстрактный базовый клас, все методы которого являются виртуальными, т.е. вызов этих методов осуществляется через специальную таблицу указателей на функции, известную как vftable. Например, вызов IUnknown::QueryInterface() преобразуется компилятором в

push        eax	               	// положить в стек второй параметр
push        esi	               	// положить в стек первый параметр
mov         ecx,dword ptr [ebp+0Ch]	// загрузить vftable в ECX
mov         edx,dword ptr [ecx]    	// получить в EDX адрес первой виртуальной функции (QueryInterface)
call        dword ptr [edx]        	// вызвать функцию по указателю.

Таким образом, наша задача сводится к поиску таких таблиц и вычислению имен каждого из методов. Для этого нужно перебрать все отладочные символы и отобрать среди них все те, чьи имена выглядят как const <имя_класса>::'vftable' или const <имя_класса>::'vftable' {for 'имя_базового_класса'}. Дальше мы можем просто трактовать область памяти, адресуемую этим символом как массив указателей на функции.

BOOL CALLBACK EnumSymbolsCallback(
            LPSTR szSymbolName,
            ULONG dwSymbolAddress,
            ULONG dwSymbolSize,
            PVOID pUserContext
            )
{
    if (szSymbolName LIKE "const  *::`vftable'*for `*`")
    {

        LPDWORD pdwMember = LPDWORD(dwSymbolAddress);
        
        PIMAGEHLP_SYMBOL pSymbol = (PIMAGEHLP_SYMBOL)alloca(
        	sizeof(IMAGEHLP_SYMBOL) + MAX_PATH);
        if (pSymbol)
        {
            DWORD dwDisplacement = 0;
            BOOL bOk;
            do
            {
                pSymbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL);
                pSymbol->MaxNameLength = MAX_PATH;
        
                bOk = ::SymGetSymFromAddr(::GetCurrentProcess(),
                	*pdwMember, &dwDisplacement, pSymbol);
                if (bOk && 0 == dwDisplacement)
                {
                    // TODO
                }
            }
            while(bOk);
       }
}

Тестовое приложениие PdbView пытается восстановить все классы заданного модуля если имеется соответствующий .pdb файл.


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


Данная статья носит чисто ознакомительный характер. Использование восстановленных интерфейсов может повлечь за собой нарушение авторских прав и преследоваться установленным порядком на территории Российской Федерации и за ее пределами.


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 0    Оценка 75        Оценить