Хакер - Препарируем TypeLibrary. Реверсим код с ActiveX и OLE Automation

Хакер - Препарируем TypeLibrary. Реверсим код с ActiveX и OLE Automation

hacker_frei

https://t.me/hacker_frei

МВК

Че­го толь­ко не при­дума­ют хит­рые кодеры, дабы осложнить работу бед­ным хакерам, лома­ющим их софт! Все уже при­вык­ли к тому, что исполня­емый модуль прог­раммы исполь­зует клас­сы, фун­кции и методы, содер­жащи­еся в нем самом либо во внеш­них динами­чес­ких биб­лиоте­ках (DLL), стан­дар­тных или не очень. Одна­ко неред­ко прог­рамма для получе­ния дан­ных или выпол­нения каких‑то дей­ствий обра­щает­ся к сис­темным служ­бам или сер­верам ActiveX, и это очень неп­рият­но. Пер­вый слу­чай гораз­до более суровый, поэто­му отло­жим его обсужде­ние на потом, а сегод­ня нач­нем с вещей поп­роще.

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

Суть в том, что в опе­раци­онной сис­теме регис­три­рует­ся некий набор управля­ющих эле­мен­тов ActiveX, содер­жащих методы и клас­сы, дос­туп к которым из любого при­ложе­ния мож­но получить при помощи этой тех­нологии. Такой эле­мент с иерар­хичес­ким опи­сани­ем содер­жащих­ся в нем клас­сов и методов называ­ется биб­лиоте­кой типов (TypeLibrary). К при­меру, дру­гая извес­тная май­кро­соф­тов­ская тех­нология .NET под­держи­вает тес­ное вза­имо­дей­ствие с такими биб­лиоте­ками. Нас­толь­ко тес­ное, что может отдель­ные клас­сы и методы в сво­их сбор­ках выносить в эти биб­лиоте­ки, а при заг­рузке сбор­ки OLE Automation сты­кует их как род­ные. В таких сбор­ках нап­рочь отсутс­тву­ет IL-код, а тела методов в самой биб­лиоте­ке пус­тые. В сегод­няшней статье я рас­ска­жу, как бороть­ся с подоб­ными явле­ниями и реконс­тру­иро­вать такой запутан­ный код.

В од­ной из сво­их пре­дыду­щих ста­тей я рас­ска­зывал о под­мене IL-кода при JIT-ком­пиляции на лету. Одна­ко быва­ют слу­чаи, ког­да IL-код в сбор­ке отсутс­тву­ет. К при­меру, раз­бира­ешь ты себе спо­кой­но некий дот­нетов­ский про­ект в каком‑нибудь dnSpy, все замеча­тель­но, ни тебе обфуска­ции, ни защиты от отладки. Трас­сиру­ешь про­вер­ку лицен­зии, и р‑раз! — про­вали­ваешь­ся в фун­кцию, в которой нет кода. Смот­ришь на биб­лиоте­ку, а она вся такая: кода нет, одни заголов­ки.

Нат­равля­ем на нее деоб­фуска­торы, в надеж­де, что код как‑то хит­ро спря­тан. Но нет, код дей­стви­тель­но отсутс­тву­ет, а при вдум­чивом ана­лизе биб­лиоте­ки в IDA или CFF вид­но, что все тела методов пус­тые. И толь­ко сей­час мы обра­щаем вни­мание, что методы помече­ны атри­бутом MethodImpl(MethodImplOptions.InternalCall). В CFFExplorer в окне Method ImplFlags тоже сто­ит гал­ка нап­ротив InternalCall. Так что же это за неведо­ма зве­руш­ка?

Нем­ного покурив теорию, мы вспо­мина­ем: этот атри­бут ука­зыва­ет сре­де выпол­нения, что она име­ет дело с вызовом натив­ного метода (не IL, а хар­дкор­ных плат­формен­но зависи­мых машин­ных кодов) из свя­зан­ной с исполня­емым фай­лом биб­лиоте­ки, которая может быть написа­на на C, C++ или даже на ASM. Подоб­ным обра­зом так­же реали­зуют­ся внут­ренние вызовы исполня­емо­го кода, нап­ример из mscorlib. Эту задачу мож­но реали­зовать, в час­тнос­ти, через атри­бут DllImport. В этом слу­чае хотя бы ясно, в какой имен­но фун­кции какой имен­но биб­лиоте­ки сле­дует искать нуж­ный код реали­зации, но в нашем при­мере соз­датели про­екта решили мак­сималь­но испортить нам жизнь. Еще нем­ного поковы­ряв куцый огры­зок кода биб­лиоте­ки, мы обна­ружи­ваем в ее заголов­ке сле­дующую конс­трук­цию:

[CoClass(typeof(CheckerClass)), Guid("3F5942E1-108B-11d4-B050-000001260696")]

[ComImport]

Сно­ва све­рив­шись с докумен­таци­ей, мы при­ходим к выводу, что наша биб­лиоте­ка слу­жит все­го лишь переход­ным интерфей­сом к COM-биб­лиоте­ке типов с дан­ным GUID. И все содер­жащи­еся в ней фун­кции авто­мати­чес­ки перет­ран­сли­руют­ся в методы соот­ветс­тву­юще­го клас­са. Бла­го в опи­сании каж­дой фун­кции есть ее индекс DispID. Поп­робу­ем най­ти эту биб­лиоте­ку типов сре­ди зарегис­три­рован­ных в сис­теме.

Для начала прос­то запус­каем regedit и ищем наш GUID. Дей­стви­тель­но, в вет­ке HKLMACHINE\SOFTWARE\Classes\Interface\ обна­ружи­вает­ся раз­дел {3F5942E1-108B-11d4-B050-000001260696}, а в нем — целых три под­разде­ла. В одном из них, озаг­лавлен­ном TypeLib, мы видим дру­гой GUID {62C8FE65-4EBB-45E7-B440-6E39B2CDBF29}. Теперь вобь­ем в поиск уже его, и наше тер­пение воз­награж­дает­ся: мы находим это зна­чение в парамет­ре TypeLib раз­дела HKEY_CLASSES_ROOT\CLSID\{67283557-1256-3349-A135-055B16327CED}. Этот GUID нам до боли зна­ком, мы видели его в заголов­ке нашей мно­гос­тра­даль­ной биб­лиоте­ки:

[ClassInterface(0), ComSourceInterfaces("LICCHECKLib._ICheckerEvents\0\0"), Guid("67283557-1256-3349-A135-055B16327CED"), TypeLibType(2)]

Этот раз­дел содер­жит мно­го инте­рес­ного, но глав­ное — в под­разде­ле InprocServer32 мы находим пол­ный путь к TypeLibrary, который мож­но пре­пари­ровать! Вооб­ще говоря, тот же резуль­тат мож­но (и нуж­но) было получить гораз­до про­ще. У Microsoft есть малень­кая, но очень полез­ная ути­лита OLE/COM Object viewer (oleview.exe). Она вхо­дит в пакет ути­лит, пос­тавля­ющих­ся вмес­те с MSVC. Мы с самого начала зна­ли имя клас­са, поэто­му дос­таточ­но запус­тить ее и най­ти этот класс в упо­рядо­чен­ном по алфа­виту раз­деле Controls.

Еще мож­но было поис­кать по име­ни клас­са и в Regedit, но у Oleview есть сущес­твен­ное пре­иму­щес­тво: в кон­текс­тном меню при выборе пун­кта View Type Information прог­рамма выда­ет всю внут­реннюю струк­туру нуж­ной биб­лиоте­ки типов, вклю­чая экспор­тиру­емые клас­сы и методы. Того же эффекта мож­но было бы добить­ся, заг­рузив в него наш OCX через File → View TypeLib. По сути дела, он деком­пилиру­ет встро­енный в биб­лиоте­ку TLB, который мож­но самому вытащить отту­да редак­тором ресур­сов (тре­буемый ресурс так и называ­ется: TYPELIB).

Ка­залось бы, все у нас хорошо, да не очень. Мы, по сути, вер­нулись на исходную позицию: у нас есть спи­сок заголов­ков методов с парамет­рами, но как получить их код — неяс­но. Нес­мотря на то что TypeLibrary пред­став­ляет собой стан­дар­тную биб­лиоте­ку Windows, в отли­чие от экспор­тиру­емых фун­кций DLL нель­зя прос­то так взять и пос­мотреть спи­сок экспор­тиру­емых методов с их точ­ками вхо­да. Все потому, что COM-объ­екты внут­ренние и не рас­кры­вают детали сво­ей реали­зации путем экспор­та фун­кций. Вмес­то это­го COM пре­дос­тавля­ет интерфейс для соз­дания экзем­пля­ров COM-клас­са через вызов CoCreateInstance с исполь­зовани­ем UUID (обыч­но извес­тно­го CLSID) в качес­тве средс­тва иден­тифика­ции клас­са COM.

Воз­вра­щаемый объ­ект — это объ­ект C++, реали­зующий набор API-интерфей­сов, которые пред­став­лены в виде таб­лицы вир­туаль­ных фун­кций для это­го COM-объ­екта. Поэто­му нет необ­ходимос­ти экспор­тировать эти фун­кции, и ты не можешь най­ти их с помощью пред­став­ления экспор­та IDA. Пос­коль­ку реали­зация дан­ной выдачи может варь­иро­вать­ся раз­работ­чиком каж­дой кон­крет­ной TypeLibrary, не сущес­тву­ет уни­вер­саль­ных методов реверс‑инжи­нирин­га для подоб­ных биб­лиотек. Хотя спра­вед­ливос­ти ради надо ска­зать, что начиная с кон­ца шес­тых вер­сий IDA силь­но эво­люци­они­рова­ла в дан­ном воп­росе.

Что ж, для начала поп­робу­ем смо­дели­ровать вызов метода из сво­ей прог­раммы. Не буду вда­вать­ся в неп­ростые под­робнос­ти прог­рамми­рова­ния COM-кли­ента, они очень под­робно и доход­чиво рас­писаны на сай­те «Пер­вые шаги». Отсю­да же берем и готовый код кли­ента:

#include "windows.h"

#include "iostream.h"

#include "initguid.h"

DEFINE_GUID(IID_Step,

0x3f5942e2, 0x108b, 0x11d4, 0xb0, 0x50, 0x0, 0x0, 0x1, 0x26, 0x6, 0x96);

class IStep : public IUnknown {

public:

IStep();

virtual ~IStep();

STDMETHOD(MyComMessage) () PURE;

};

void main() {

cout << "Initializing COM" << endl;

if( FAILED( CoInitialize( NULL ) ) ) {

cout << "Unable to initialize COM" << endl;

return ;

}

CLSID clsid;

HRESULT hr = ::CLSIDFromProgID( L"LicCheck.Checker.1", &clsid );

if( FAILED( hr ) ) {

cout << "Unable to get CLSID " << endl;

return ;

}

IClassFactory* pCF;

hr = CoGetClassObject( clsid,

CLSCTX_INPROC,

NULL,

IID_IClassFactory,

(void**) &pCF );

if ( FAILED( hr ) ) {

cout << "Failed to GetClassObject " << endl;

return ;

}

IUnknown* pUnk;

hr = pCF->CreateInstance( NULL, IID_IUnknown, (void**) &pUnk );

pCF->Release();

if( FAILED( hr ) ) {

cout << "Failed to create server instance " << endl;

return ;

}

cout << "Instance created" << endl;

IStep* pStep = NULL;

hr = pUnk->QueryInterface( IID_Step, (void**) &pStep );

pUnk->Release();

if( FAILED( hr ) ) {

cout << "QueryInterface() for IStep failed" << endl;

CoUninitialize();

return ;

}

pStep->MyComMessage();

pStep->Release();

cout << "Shuting down COM" << endl;

CoUninitialize();

}

В мак­росе DEFINE_GUID мы пос­тавили свой GUID, что­бы обра­щение велось имен­но к нашему клас­су. Не будем замора­чивать­ся и менять объ­явле­ние клас­са IStep, в нем уже есть один метод. Нас, по сути, инте­ресу­ет реали­зация самой таб­лицы адре­сов. Мы даже не будем возить­ся с парамет­рами, хотя если мы нач­нем вдум­чиво и пол­ноцен­но копать кон­крет­ный метод в отладчи­ке, то нам таки при­дет­ся это делать. Одна­ко в пер­вом приб­лижении для прос­тоты при­мера опус­тим эти мелочи.

Итак, ском­пилиро­вав этот любез­но пре­дос­тавлен­ный авто­ром при­мер, заг­рузив его в отладчик и исполняя дан­ный код пошаго­во, мы замеча­ем, что пос­ле вызова CoGetClassObject наша биб­лиоте­ка типов заг­ружа­ется в память про­цес­са и на нее уже мож­но ста­вить бря­ки. А pUnk->QueryInterface воз­вра­щает собс­твен­ный ука­затель на ука­затель на таб­лицу вир­туаль­ных методов 1012E1DC. И тут нас сно­ва ждет облом: это явно не та таб­лица, которую мы ищем.

При даль­нейшем изу­чении вид­но, что в най­ден­ной таб­лице все­го пять методов, а у нас свы­ше полусот­ни, да и код этих методов по переда­ваемым парамет­рам явно не соот­ветс­тву­ет заголов­кам. Нем­ножко подумав, мы понима­ем, в чем дело: пред­ложен­ный при­мер реали­зовы­вал­ся через фаб­рику клас­сов. Собс­твен­но, CoGetClassObject и воз­вра­щает интерфейс этой самой фаб­рики, содер­жащей­ся в биб­лиоте­ке типов. Одна­ко нам нуж­на не она, нам нужен наш род­ной класс Checker, точ­нее, его таб­лица вир­туаль­ных методов.

Яс­но, что в дан­ной биб­лиоте­ке таких таб­лиц как собак нереза­ных и, что­бы получить дос­туп имен­но к нуж­ной, надо дол­го и упор­но изу­чать чудовищ­но запутан­ные и нелогич­ные май­кро­соф­тов­ские ману­алы по COM, которые (что обид­нее все­го) нам, ско­рее все­го, и не понадо­бят­ся вне решения дан­ной задачи. Поэто­му откла­дыва­ем пока упо­мяну­тый спо­соб как запас­ной (как толь­ко у нас появит­ся адрес нуж­ной таб­лицы, его, нес­мотря ни на что, впол­не мож­но исполь­зовать для гряз­ной отладки методов) и воз­вра­щаем­ся к IDA.

Как я уже говорил, на пер­вый взгляд все выг­лядит так же уны­ло. Из биб­лиоте­ки тор­чат уши четырех экспор­тиру­емых фун­кций: DllCanUnloadNowDllGetClassObjectDllRegisterServer и DllUnregisterServer, при помощи которых отыс­кать нуж­ную таб­лицу весь­ма проб­лематич­но. Наз­вания методов в коде (помимо встро­енно­го ресур­са TYPELIB) тоже отсутс­тву­ют, и вооб­ще дан­ный раз­дел весь­ма сла­бо задоку­мен­тирован. По счастью, наш­лись умные люди, которые рас­ковыря­ли прин­цип раз­мещения таб­лиц вир­туаль­ных адре­сов в биб­лиоте­ке типов, ском­пилиро­ван­ных ком­пилято­ром C++.

Ра­зуме­ется, эти дан­ные получе­ны чис­то эмпи­ричес­ки и зависят от реали­зации ком­пилято­ра, поэто­му все гаран­тии, что так будет вез­де и всег­да, весь­ма смут­ны и осно­ваны на сов­мести­мос­ти с сог­лашени­ями о вызовах COM, которые тре­буют пос­ледова­тель­ного наз­начения сло­тов для вир­туаль­ных фун­кций. Базовая струк­тура, которая опи­сыва­ет каж­дый класс, — так называ­емый RTTI Complete Object Locator — выг­лядит вот так:

typedef const struct _s__RTTICompleteObjectLocator {

unsigned long signature;

unsigned long offset;

unsigned long cdOffset;

_TypeDescriptor *pTypeDescriptor;

__RTTIClassHierarchyDescriptor *pClassDescriptor;

} __RTTICompleteObjectLocator;

Нам нуж­ны в ней два поля (осталь­ные пус­тые) — pTypeDescriptor, которое ука­зыва­ет на имя клас­са, и вто­рое, ука­зыва­ющее на опи­сатель иерар­хии базовых клас­сов pClassDescriptor. Пос­ледний содер­жит количес­тво базовых клас­сов и ука­затель на их мас­сив:

typedef const struct _s__RTTIClassHierarchyDescriptor {

unsigned long signature;

unsigned long attributes;

unsigned long numBaseClasses;

__RTTIBaseClassArray *pBaseClassArray;

} __RTTIClassHierarchyDescriptor;

Дан­ный мас­сив содер­жит ука­зате­ли, ука­зыва­ющие, в свою оче­редь, на TypeDescriptor каж­дого базово­го клас­са, при­чем обыч­но пер­вый из них — наш класс. Для наг­ляднос­ти при­веду схе­му раз­мещения дан­ных струк­тур и их свя­зи друг c дру­гом на при­мере кон­крет­ного клас­са ATL::CComClassFactory. Таб­лицу вир­туаль­ных методов которо­го, кста­ти ска­зать, воз­вра­щает код из опи­сан­ного выше при­мера.

Таб­лица вир­туаль­ных методов

Как вид­но из рисун­ка, у него шесть базовых клас­сов, vftable сос­тоит из пяти методов. Исхо­дя из ска­зан­ного выше, при­меня­ем эмпи­ричес­кий прин­цип поис­ка vftable для каж­дого клас­са. Пос­коль­ку ком­пилятор обыч­но дает ком­пилиро­ван­ным клас­сам име­на, начина­ющиеся с .?AV, то тупо ищем в коде TypeDescriptor по дан­ной сиг­натуре. По ссыл­ке на него ищем RTTI Complete Object Locator, по ссыл­ке на который, в свою оче­редь, vftable. Код питонов­ско­го скрип­та для IDA, реали­зующе­го дан­ный поиск, при­веден ниже (код взят из статьи в бло­ге Quarkslab):

# IDA Python RTTI parser ~pod2g 06/2013

from idaapi import *

from idc import *

# TODO: test on 64bit !!!

addr_size = 4

first_seg = FirstSeg()

last_seg = FirstSeg()

for seg in Segments():

if seg > last_seg:

last_seg = seg

if seg < first_seg:

first_seg = seg

def get_pointer(ea):

if addr_size == 4:

return Dword(ea)

else:

return Qword(ea)

def in_image(ea):

return ea >= first_seg and ea <= SegEnd(last_seg)

def get_class_name(name_addr):

s = Demangle('??_7' + GetString(name_addr + 4) + '6B@', 8)

if s != None:

return s[0:len(s)-11]

else:

return GetString(name_addr)

start = first_seg

while True:

# Ищем в коде сигнатуру ".?AV" — обычно так начинаются классы C++

f = FindBinary(start, SEARCH_DOWN, "2E 3F 41 56")

start = f + addr_size

if f == BADADDR:

break

rtd = f - 8

# Преобразуем в нормальное имя и печатаем

print "Found class: %s (rtd=0x%X)" % (get_class_name(f), rtd)

# Ищем все ссылки на смещение начала класса — 8

for xref in XrefsTo(rtd):

# Следующее слово — смещение rchd

rchd = get_pointer(xref.frm + addr_size)

# На всякий случай, вдруг случайное левое смещение

if in_image(rchd):

# rcol — RTTI Complete Object Locator

rcol = xref.frm - 12

# rchd + 8 — количество базовых классов

rchd_numBaseClasses = Dword(rchd + 8)

# rchd + 12 — их массив, который по очереди перебираем

rchd_pBaseClassArray = get_pointer(rchd + 12)

for i in range(rchd_numBaseClasses):

rbcd = get_pointer(rchd_pBaseClassArray + addr_size * i)

# Каждый элемент массива — указатель на базовый класс

rbcd_pTypeDescriptor = get_pointer(rbcd)

rbcd_pTypeDescriptor_name = get_class_name(rbcd_pTypeDescriptor + 8)

print " - base class: %s" % rbcd_pTypeDescriptor_name

# Ссылка на RTTI Complete Object Locator — vtable

for xref in XrefsTo(rcol):

vtable = xref.frm + addr_size

break

print " - vtable: 0x%X" % vtable

Как видишь, прин­цип дос­таточ­но прост: обра­щаю вни­мание, что сущес­тву­ющий код заточен под 32-бит­ную архи­тек­туру, под 64-бит­ную его при­дет­ся допили­вать, как минимум поменяв раз­мер сло­ва addr_size = 8, хотя, на пер­вый взгляд, дан­ный прин­цип пос­тро­ения кода харак­терен и для нее.

Нат­равив дан­ный скрипт на нашу биб­лиоте­ку типов, получа­ем в логе IDA длин­нющую прос­тыню, ибо клас­сов в биб­лиоте­ке содер­жится великое мно­жес­тво. Одна­ко в самом начале ее мы видим сле­дующее:

Found class: ATL::CComObjectCached<ATL::CComClassFactory> (rtd=0x10155070)

- base class: ATL::CComObjectCached<ATL::CComClassFactory>

- base class: ATL::CComClassFactory

- base class: IClassFactory

- base class: IUnknown

- base class: ATL::CComObjectRootEx<ATL::CComMultiThreadModel>

- base class: ATL::CComObjectRootBase

- vtable: 0x1012E1DC

Found class: ATL::CComClassFactory (rtd=0x101550BC)

- base class: ATL::CComClassFactory

- base class: IClassFactory

- base class: IUnknown

- base class: ATL::CComObjectRootEx<ATL::CComMultiThreadModel>

- base class: ATL::CComObjectRootBase

- vtable: 0x1012E1F8

Found class: ATL::CComObjectRootEx<ATL::CComMultiThreadModel> (rtd=0x10155128)

Found class: ATL::CComObjectRootBase (rtd=0x10155178)

Found class: ATL::CComObject<CChecker> (rtd=0x101551A4)

- base class: ATL::CComObject<CChecker>

- base class: CChecker

- base class: ATL::CComObjectRootEx<ATL::CComSingleThreadModel>

- base class: ATL::CComObjectRootBase

- base class: ATL::IDispatchImpl<IChecker,&_GUID const IID_IChecker,&_GUID const LIBID_LICCHECKLib,1,0,ATL::CComTypeInfoHolder>

- base class: IChecker

- base class: IDispatch

- base class: IUnknown

- base class: ATL::CComControl<CChecker,ATL::CWindowImpl<CChecker,ATL::CWindow,ATL::CWinTraits<1442840576,0>>>

- base class: ATL::CComControlBase

- base class: ATL::CWindowImpl<CChecker,ATL::CWindow,ATL::CWinTraits<1442840576,0>>

- base class: ATL::CWindowImplBaseT<ATL::CWindow,ATL::CWinTraits<1442840576,0>>

- base class: ATL::CWindowImplRoot<ATL::CWindow>

- base class: ATL::CWindow

- base class: ATL::CMessageMap

- base class: ATL::IPersistStreamInitImpl<CChecker>

- base class: IPersistStreamInit

- base class: IPersist

- base class: IUnknown

- base class: ATL::IOleControlImpl<CChecker>

- base class: IOleControl

- base class: IUnknown

- base class: ATL::IOleObjectImpl<CChecker>

- base class: IOleObject

- base class: IUnknown

- base class: ATL::IOleInPlaceActiveObjectImpl<CChecker>

- base class: IOleInPlaceActiveObject

- base class: IOleWindow

- base class: IUnknown

- base class: ATL::IViewObjectExImpl<CChecker>

- base class: IViewObjectEx

- base class: IViewObject2

- base class: IViewObject

- base class: IUnknown

- base class: ATL::IOleInPlaceObjectWindowlessImpl<CChecker>

- base class: IOleInPlaceObjectWindowless

- base class: IOleInPlaceObject

- base class: IOleWindow

- base class: IUnknown

- base class: ISupportErrorInfo

- base class: IUnknown

- base class: ATL::IConnectionPointContainerImpl<CChecker>

- base class: IConnectionPointContainer

- base class: IUnknown

- base class: ATL::IPersistStorageImpl<CChecker>

- base class: IPersistStorage

- base class: IPersist

- base class: IUnknown

- base class: ATL::ISpecifyPropertyPagesImpl<CChecker>

- base class: ISpecifyPropertyPages

- base class: IUnknown

- base class: ATL::IQuickActivateImpl<CChecker>

- base class: IQuickActivate

- base class: IUnknown

- base class: ATL::IDataObjectImpl<CChecker>

- base class: IDataObject

- base class: IUnknown

- base class: ATL::IProvideClassInfo2Impl<&_GUID const CLSID_Checker,&_GUID const DIID__ICheckerEvents,&_GUID const LIBID_LICCHECKLib,1,0,ATL::CComTypeInfoHolder>

- base class: IProvideClassInfo2

- base class: IProvideClassInfo

- base class: IUnknown

- base class: ATL::IPropertyNotifySinkCP<CChecker,ATL::CComDynamicUnkArray>

- base class: ATL::IConnectionPointImpl<CChecker,&_GUID const IID_IPropertyNotifySink,ATL::CComDynamicUnkArray>

- base class: ATL::_ICPLocator<&_GUID const IID_IPropertyNotifySink>

- base class: ATL::CComCoClass<CChecker,&_GUID const CLSID_Checker>

- vtable: 0x1012E6A4

То есть адрес таб­лицы нашего клас­са Checker — 1012E6A4 очень похож на нас­тоящий. Количес­тво ссы­лок, во вся­ком слу­чае, сов­пада­ет. Для при­мера берем код метода по про­изволь­ной ссыл­ке: количес­тво переда­ваемых парамет­ров вро­де как в нор­ме. Что ж, мож­но нас поз­дра­вить, похоже, мы наш­ли таб­лицу вир­туаль­ных методов. Опи­раясь на которую, мож­но с опре­делен­ной долей уве­рен­ности ста­вить точ­ки оста­нова в отладчи­ке или реконс­тру­иро­вать код. Вооб­ще говоря, при опре­делен­ной доле сно­ров­ки мож­но было бы обой­тись и без скрип­та.

Пла­гин СlassInformer для IDA

К при­меру, еще с кон­ца 6-х вер­сий IDA был соз­дан пла­гин СlassInformer, помога­ющий в поис­ке и раз­борке RTTI (хотя, спра­вед­ливос­ти ради, у меня так тол­ком и не получи­лось запус­тить его в пол­ном объ­еме ни на одной вер­сии IDA из име­ющих­ся под рукой). Тем более седь­мые вер­сии и сами уме­ют искать и отоб­ражать RTTI Complete Object Locator, чего, в прин­ципе, впол­не дос­таточ­но для поис­ка vftable.

Лож­ка дег­тя в том, что на один и тот же RTTI Complete Object Locator могут ссы­лать­ся нес­коль­ко vftable, то есть пол­ностью «однокно­поч­ного» решения дан­ный метод не дает и всег­да есть мес­то для хакер­ской инту­иции.

Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei



Report Page