eXTracted INternals

eXTracted INternals
 
ФорумФорум  ЧаВоЧаВо  ПоискПоиск  РегистрацияРегистрация  ПользователиПользователи  ГруппыГруппы  Вход  

Поделиться | 
 

 Реверсинг OLE/COM обьектов

Предыдущая тема Следующая тема Перейти вниз 
АвторСообщение
Hex

avatar

Количество сообщений : 397
Возраст : 35
Дата регистрации : 2006-07-12

СообщениеТема: Реверсинг OLE/COM обьектов   Ср 19 Июл - 1:56

Статей по этому поводу не видел вообще. Решил поделиться мыслями и знаниями.

Скажу сразу, что я не читал Дональда Бокса и не написал ни одного COM объекта. Тем не менее, это мне никак не помешало заниматься реверсингом функциональности COM объектов.

Все таки есть набор знаний, который нужен для того чтобы успешно реверсить COM объекты.

Интерфейсы
COM объекты - это самые обычные объекты C++. Фишка в том, что часть их виртуальных функций "торчит наружу". То есть механизм с помощью, которого можно получить указатель на таблицу виртуальных функций COM объекта. Набор функций который "торчит наружу" и называется интерфейсом. Основной способ работы с COM объектами - это получение указателя на интерфейс, т.е. на таблицу виртуальных функций. А далее функции вызываются также как обычные виртуальные функции любого объекта.

CLSID и IID
Для того чтобы идентифицировать COM объекты и их интерфейсы используются идентификаторы CLSID (Class ID) и IID (Interface ID). Благодаря CLSID можно создать COM объект, а по IID можно получить указатель на интерфейс. То есть все COM объекты уникальны, и идентифицируются по CLSID. Когда нужен новый COM объект - генерят новый CLSID. Список CLSID лежит в реестре в ключе HKEY_CLASSES_ROOT\CLSID. Для того чтобы COM объектом можно было пользоваться его регистрируют в системе, тем самым добавляя новую запись в ключ реестра HKEY_CLASSES_ROOT\CLSID. Когда приложение хочет создать СОМ объект оно вызывает соответсвующую апи, а эта апи ищет в реестре ключ, узнает в каком файле находится реализация СОМ объекта и стартует его. IID нужен для того чтобы получать таблицу виртуальных функций. У COM объекта может быть много интерфейсов, т.е. много таблиц виртуальных функций, которые он предоставляет. Поэтому чтобы COM объект мог предоставить какую-то конкретную из них используются IID. И CLSID и IID - это структура GUID:

GUID struc ; (sizeof=0x10, standard type)
Data1 dd ?
Data2 dw ?
Data3 dw ?
Data4 db 8 dup(?)
GUID ends

Выглядят они где-то так: {00000000-0F56-11D2-9887-00A0C969725B}


Как используются CLSID и IID
COM объекты создаются с помощью функций CoCreateInstance(), CoCreateInstanceEx(), CoGetClassObject(). Все они создают COM объекты, но каждая по своему. Я буду рассматривать на примере CoCreateInstance(), потому что она чаще всего встречается.

STDAPI CoCreateInstance(
REFCLSID rclsid,
LPUNKNOWN pUnkOuter,
DWORD dwClsContext,
REFIID riid,
LPVOID * ppv
);

Куча умных новых типов в объявлении функции. По-человечески это выглядит вот так:

DWORD __stdcall CoCreateInstance(
PGUID rclsid,
PVOID pUnkOuter,
DWORD dwClsContext,
PGUID riid,
LPVOID * ppv
);

Параметры которые интересуют нас при реверсинге:
rclsid - указатель на CLSID класса
riid - указатель на IID клаcса
ppv - двойной указатель на интерфейс. Т.е. в эту переменную возвращается указатель на интерфейс.

И оставшиеся параметры:
pUnkOuter - указатель на COM объект, который агрегирует в себя создаваемый объект. Страшное слово означающее что создаваемый СОМ объект будет являться частью другого объекта.

dwClsContext - способ того как создавать СОМ объект.
Если он реализован в dll, то будет просто загружена dll.
Если он реализован в EXE, то объект может быть создан во внешнем процессе (т.е. будет запущен exe файл), или может быть создан внутри этого же процесса (т.е. exe будет загружен как DLL).
Там еще много разных вариантов, но лучше себе голову этим не забивать.

Стандартные интерфейсы
Есть набор интерфейсов от которых наследуются все пользовательские COM объекты. Самые важные из них: IUnknown и IDispatch.

Код:

IID_IUnknown    dd 0                    ; Data1
                dw 0                    ; Data2
                dw 0                    ; Data3
                db 0C0h, 6 dup(0), 46h  ; Data4

IUnknown - базовый интерфейс, от него наследуются все СОМ объекты. Он содержит 3 функции:
QueryInterface() - получить указатель на интерфейс.
AddRef() - Увеличить счетчик ссылок объекта.
Release() - Уменьшить счетчик ссылок объекта. Когда счетчик ссылок равен 0 - объект автоматически уничтожается.

IDispatch - содержит в себе функции IUnknown.
Хитрый интерфейс, который позволяет вызывать функции так называемых Dispatch интерфейсов. Прикол тут в том, что функции могут вызываться не напрямую (через таблицу виртуальных функций), а по ID функции.

Полную инфу по интерфейсам IUnknown и IDispatch можно найти в MSDN.

Обычно, СОМ объект создают с IID IUnknown, для того чтобы узнать существует ли объект. После того как объект создан и есть указатель на его интерфейс IUnknown. Вызывают функцию IUnknown->QueryInterface для того, чтобы получить указатель на какой-то пользовательский интерфейс. Некоторые так не поступают и создают класс сразу по IID'у пользовательского интерфейса.

Библиотеки типов
Для того чтобы одни программисты могли использовать COM объекты других программистов, были придуманы библиотеки типов (Type Library). Это .tlb файлы которые содержат информацию о СОМ объектах, интерфейсах, константах, структурах которые передаются в качестве параметров. Такие библиотеки обычно даются с SDK. Но часто они просто идут с программой, или даже прикреплены в секцию ресурсов исполнимого файла. Библиотеки типов - это незаменимый источник информации. Благодаря им сразу понятно где какой класс, какая функция и какие у нее параметры. Чтобы просматривать библиотеки типов лучше всего использовать тулзу OLE/COM object viewer. Она входит в состав Visual Studio.


Пример восстановления интерфейса
Начнем с чего-нить простого. Например, восстановим интерфейс СОМ объекта которым пользуется виндовый dialer.exe
Код:

.text:0101CC59 004                push    esi            ; ppv
.text:0101CC5A 008                push    offset stru_10084E4 ; riid
.text:0101CC5F 00C                push    1              ; dwClsContext
.text:0101CC61 010                push    0              ; pUnkOuter
.text:0101CC63 014                push    offset stru_10084D4 ; rclsid
.text:0101CC68 018                call    ds:CoCreateInstance

.text:010084D4    ; CLSID stru_10084D4
.text:010084D4    stru_10084D4    dd 0F1029E5Bh          ; Data1
.text:010084D4                    dw 0CB5Bh              ; Data2
.text:010084D4                    dw 11D0h                ; Data3
.text:010084D4                    db 8Dh, 59h, 0, 0C0h, 4Fh, 0D9h, 1Ah, 0C0h; Data4

.text:010084E4    ; IID stru_10084E4
.text:010084E4    stru_10084E4    dd 34621D6Bh            ; Data1
.text:010084E4                                            ; DATA XREF: sub_101CC4E+Co
.text:010084E4                    dw 6CFFh                ; Data2
.text:010084E4                    dw 11D1h                ; Data3
.text:010084E4                    db 0AFh, 0F7h, 0, 0C0h, 4Fh, 0C3h, 1Fh, 0EEh; Data4

Смотрим в реестр ключ соответсвующий stru_10084D4:
HKEY_CLASSES_ROOT\CLSID\{F1029E5B-CB5B-11D0-8D59-00C04FD91AC0}

Ага, это "Rendezvous Class", а где он реализован? Смотрим дальше:
HKEY_CLASSES_ROOT\CLSID\{F1029E5B-CB5B-11D0-8D59-00C04FD91AC0}\InprocServer32
C:\WINDOWS\system32\rend.dll

Теперь нам на помощь приходит тулза OLE/COM object viewer. Открываем ею, через "view typelib", rend.dll и переключаем в "view" "group by kind". Заходим в "папку" Interfaces и ищем там наш riid... Это ITRendezvous.

Смотрим теперь полнее.
Код:

.text:0101CC4E    CreateRendezvous proc near              ; CODE XREF: sub_101D37A+Fp
.text:0101CC4E 000                mov    edi, edi
.text:0101CC50 000                push    esi
.text:0101CC51 004                lea    esi, [ecx+4]
.text:0101CC54 004                cmp    dword ptr [esi], 0
.text:0101CC57 004                jnz    short loc_101CC73
.text:0101CC59 004                push    esi            ; ppv
.text:0101CC5A 008                push    offset ITRendezvous ; riid
.text:0101CC5F 00C                push    1              ; dwClsContext
.text:0101CC61 010                push    0              ; pUnkOuter
.text:0101CC63 014                push    offset CLSID_Rendezvous ; rclsid
.text:0101CC68 018                call    ds:CoCreateInstance
.text:0101CC6E 004                cmp    dword ptr [esi], 0
.text:0101CC71 004                jz      short loc_101CC7B
.text:0101CC73
.text:0101CC73    loc_101CC73:                            ; CODE XREF: CreateRendezvous+9j
.text:0101CC73 004                mov    eax, [esi]
.text:0101CC75 004                mov    ecx, [eax]
.text:0101CC77 004                push    eax
.text:0101CC78 008                call    dword ptr [ecx+4]
.text:0101CC7B
.text:0101CC7B    loc_101CC7B:                            ; CODE XREF: CreateRendezvous+23j
.text:0101CC7B 004                mov    eax, [esi]
.text:0101CC7D 004                pop    esi
.text:0101CC7E 000                retn
.text:0101CC7E    CreateRendezvous endp

После CoCreateInstance в esi должен быть указатель на интерфейс ITRendezvous. Дальше проверяется, чтобы там был не ноль и вызывается какая-то виртуальная функция этого интерфейса. Как узнать что за функция? Нужно восстановить структуру интерфейса. Для этого смотрим в OLE/COM object viewer Inherited Interfaces для ITRendezvous. Там IDispatch. Теперь догружаем "Type library" для иды (Shift-F11), это не .tlb, у иды они свои. Нам нужна mssdk. Теперь добавляем себе стандартную структуру IDispatchVtbl Именно Vtbl, а не просто IDispatch. Должно получиться вот такое:
Код:

00000000 IDispatchVtbl  struc ; (sizeof=0x1C, standard type)
00000000 QueryInterface  dd ?                    ; offset
00000004 AddRef          dd ?                    ; offset
00000008 Release        dd ?                    ; offset
0000000C GetTypeInfoCount dd ?                  ; offset
00000010 GetTypeInfo    dd ?                    ; offset
00000014 GetIDsOfNames  dd ?                    ; offset
00000018 Invoke          dd ?                    ; offset
0000001C IDispatchVtbl  ends
Базовый интерфейс у нас есть.

Теперь надо нарастить функции ITRendezvous. OLE/COM object viewer показывает функции интерфейса в том порядке, в котором они расположены в интерфейсе то есть интерфейс ITRendezvous выглядит следующим образом:
Код:

00000000 ITRendezvousVtbl struc ; (sizeof=0x2C)
00000000 IDispatchVtbl  IDispatchVtbl ?
0000001C DefaultDirectories dd ?                ; offset
00000020 EnumerateDefaultDirectories dd ?        ; offset
00000024 CreateDirectory dd ?                    ; offset
00000028 CreateDirectoryObject dd ?              ; offset
0000002C ITRendezvousVtbl ends

Сначала идет базовый интерфейс, а потом попорядку "пользовательские функции". Интерфейс восстановлен. Посмотрим что же за виртуальная функция вызывалась...
Код:

.text:0101CC4E    CreateRendezvous proc near              ; CODE XREF: sub_101D37A+Fp
.text:0101CC4E 000                mov    edi, edi
.text:0101CC50 000                push    esi
.text:0101CC51 004                lea    esi, [ecx+4]
.text:0101CC54 004                cmp    dword ptr [esi], 0
.text:0101CC57 004                jnz    short loc_101CC73
.text:0101CC59 004                push    esi            ; ppv
.text:0101CC5A 008                push    offset ITRendezvous ; riid
.text:0101CC5F 00C                push    1              ; dwClsContext
.text:0101CC61 010                push    0              ; pUnkOuter
.text:0101CC63 014                push    offset CLSID_Rendezvous ; rclsid
.text:0101CC68 018                call    ds:CoCreateInstance
.text:0101CC6E 004                cmp    dword ptr [esi], 0
.text:0101CC71 004                jz      short loc_101CC7B
.text:0101CC73
.text:0101CC73    loc_101CC73:                            ; CODE XREF: CreateRendezvous+9j
.text:0101CC73 004                mov    eax, [esi]
.text:0101CC75 004                mov    ecx, [eax]
.text:0101CC77 004                push    eax            ; This
.text:0101CC78 008                call    [ecx+ITRendezvousVtbl.IDispatchVtbl.AddRef]
.text:0101CC7B
.text:0101CC7B    loc_101CC7B:                            ; CODE XREF: CreateRendezvous+23j
.text:0101CC7B 004                mov    eax, [esi]
.text:0101CC7D 004                pop    esi
.text:0101CC7E 000                retn
.text:0101CC7E    CreateRendezvous endp

пойдем чуть дальше и посмотрим другие вызовы:
Код:

.text:0101D389 020                call    CreateRendezvous
.text:0101D38E 020                mov    esi, eax
...
.text:0101D3A4 020                push    ecx
.text:0101D3A5 024                mov    [ebp-18h], edi
.text:0101D3A8 024                mov    eax, [esi]
.text:0101D3AA 024                push    esi
.text:0101D3AB 028                call    [eax+ITRendezvousVtbl.EnumerateDefaultDirectories]
...
.text:0101D486 064                mov    eax, [esi]
.text:0101D488 064                push    esi            ; This
.text:0101D489 068                call    [eax+ITRendezvousVtbl.IDispatchVtbl.Release]

Можно заметить, что при создании объекта ему делают AddRef, а в конце Release. Так можно отслеживать "жизнь" СОМ объекта.

Конвенция вызовов
Все функции интерфейсов COM объектов - stdcall. Указатель на сам COM объект передается неявно (ни в одной декларации функции вы его не найдете) и передается первым аргументом.

Стековые проблемы IDA
При реверсинге кода использующего СОМ объекты возникает 2 проблемы:

1)Стековые смещения
Когда COM объект использует функция без EBP-фрейма, IDA не может просчитать на сколько байт нужно сместить стек после вызова виртуальной функции. Т.е. она считает все функции cdecl. Тут ничего сделать нельзя - только вручную указывать после каждого вызова.

2) EH_Prolog/SEH_Prolog
Компилятор вместо обычного:
push ebp
mov ebp, esp
(потом установка стека и SEH)

использует:
.text:0101D37A 000 mov eax, offset dword_10334F4
.text:0101D37F 000 call __EH_prolog

IDA не понимает эту __EH_prolog, ну просто никак. И отказывается анализировать стек. Казалось бы, сделал функции "BP Based Frame" и все ок. Да не тут-то было аргументы съезжают на 4 байта. Начинается полный бардак. Для решения этой проблемы юзаем скрипт:
Код:

#include <idc.idc>

static main(void)
{
auto a,i,s;
a=LocByName("__EH_prolog");
if(a==BADADDR) return;
Message("__EH_prolog = %08X\n",a);
i=RfirstB(a);
while(i!=BADADDR)
{
Message("- %08X - ",i);
if (SetFunctionFlags(i,GetFunctionFlags(i) | FUNC_FRAME))
{
s = LocByName(GetFunctionName(i));
if (s != BADADDR)
{
MakeFrame(s,GetFrameLvarSize(s), GetFrameRegsSize(s)+4, GetFrameArgsSize(s));
AnalyseArea(s,FindFuncEnd(i)+1);
}
Message("OK\n");
}
else
Message("Error\n");
i=RnextB(a,i);
}
}
Юзать надо после завершения автоанализа и только однократно, а то все испортит. Не помню кто со мной поделился им на reng.ru но все равно спасибо.
Вернуться к началу Перейти вниз
Посмотреть профиль
 
Реверсинг OLE/COM обьектов
Предыдущая тема Следующая тема Вернуться к началу 
Страница 1 из 1

Права доступа к этому форуму:Вы не можете отвечать на сообщения
eXTracted INternals :: Cтатьи :: Win32 reversing-
Перейти: