Че­го толь­ко не при­дума­ют хит­рые кодеры, дабы осложнить работу бед­ным хакерам, лома­ющим их софт! Все уже при­вык­ли к тому, что исполня­емый модуль прог­раммы исполь­зует клас­сы, фун­кции и методы, содер­жащи­еся в нем самом либо во внеш­них динами­чес­ких биб­лиоте­ках (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. И тут нас сно­ва ждет облом: это явно не та таб­лица, которую мы ищем.

Продолжение доступно только участникам

Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

Вариант 2. Открой один материал

Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии