Данная статья является плодом моих собственных исследований и опыта, накопленного при создании компонентов ActiveX (используя Delphi 3.0 и Visual C++ 5.0 (как с помощью MFC, так и ATL)). Я не видел нигде в Сети (включая приснопамятную Fravia) сколько-нибудь внятного объяснения принципов строения компонентов ActiveX с точки зрения Reverse Engeneeringа, несмотря на то, что данная технология становится всё более распространённой - по моим наблюдениям, количество ActiveX компонентов уже давно превысило число компонентов Delphi и модулей для Perl'а, вместе взятых. Ваши замечания и предложения можете присылать автору по адресу redplait@usa.net. Данная статья предполагает знакомство читателя с моделью COM, знание ассемблера и умение использовать дизассемблер IDA Pro. Вам понадобятся программы:
ShotGraph - это небольшой
ActiveX компонент, позволяющий из любой
скриптовой программы, являющейся
контроллером OLE Automation (например, Visual
Basic, Visual Basic for Applications, Active Server Pages etc) , в
полёте строить и сохранять GIF и JPEG
картинки. Взять можно: Однако, там лежит shareware
версия с неполными функциональными
возможностями, обладающая следующими
ограничениями (взято из README файла):
Как видим, ограничения слишком существенны, чтобы можно было серьёзно использовать этот компонент. Хочется выяснить, действительно ли некоторые методы не реализованы, и, даже если они физически отсутствуют, есть шанс побороть ограничения. Итак, загружаем shotgraph.dll (молча инсталлировавшуюся в системный каталог Windows) в IDA Pro (я использовал старую добрую версию 3.76), и пока она дизассемблируется, вспомним (кто знал, конечно), что представляют собой OLE Automation и компоненты ActiveX.
Различают клиентов (или контроллеры) автоматизации - приложения, использующие функциональность других приложений, и сервера автоматизации - программы, предоставляющие другим приложениям свои функциональные возможности. Нужно отметить, что сервера автоматизации могут являться одновременно и контроллерами автоматизации, так же возможна и другая картина. Компоненты ActiveX являются серверами OLE Automation (но могут быть и контроллерами).
Любой COM объект должен реализовывать как минимум один такой интерфейс IUnknown, управляющий временем жизни COM объектов и предназначенный для запроса у COM объектов указателей на другие интерфейсы. Также, чтобы некий COM объект стал объектом OLE Automation, он должен реализовать ещё один интерфейс - IDispatch. Более подробно об этом, и о методах этих интерфейсов, Вы сможете прочесть в "OLE Programmers Reference". Также кроме этих интерфейсов, дабы представлять собою нечто полезное, COM объект,как правило, реализует ещё один или несколько интерфейсов, которыми собственно и пользуются контроллеры OLE Automation. А раз так, то где-то в исследуемой программе должна иметься таблица, содержащая указатели на предоставляемые функции, в том числе и на якобы нереализованные методы BuildPalette and company. Звучит странно - указатель на нереализованный метод, не правда ли? Но Вы должны помнить - если интерфейс описан, его методы могут быть вызваны без всяких проблем. Если вместо указателя на такую нереализованную функцию помещён, скажем, NULL указатель - это значит, что программа-контроллер потерпит крах при попытке вызова такого метода! Однако этого не случается - значит, некий код всё-таки вызывается (другое дело, что он может не делать ничего, кроме возврата кода ошибки). На самом деле всё вышеописанное (как и всё в исполнении фирмы Microsoft) представляет собою более сложные вещи, например, возможно объединение двух и более интерфейсов (точнее, двух и более таблиц указателей на функции) в одну таблицу (так называемый дуальный интерфейс), однако предоставленной информации должно быть достаточно для понимания того, с чем мы будем иметь дело. Кроме того, Вы должны задать мне вопрос - откуда контроллер автоматизации знает, какие функции есть для него у сервера автоматизации? Это хороший вопрос. Дело в том, что такая информация хранится в двоичном виде в специальном формате - в библиотеке типов. Библиотека типов может быть либо отдельным файлом, поставляемым вместе с компонентом ActiveX, либо быть подлинкованным к самому компоненту в виде двоичного ресурса. Самое важное - то, что Вы можете её просматривать так же, как это делает любой контроллер OLE Automation. Для этого нам потребуется какой-нибудь инструмент просмотра библиотек типов - например, программа oleview.exe из Visual C++ 5.0. Здесь мы имеем дело с файлом .dll (также может иметь расширение .ocx) - так называемый внутризадачный COM компонент; его выполнение происходит в контексте вызывающего процесса. Эта .dll должна соответствовать некоторым требованиям, налагаемым на двоичный формат компонентов ActiveX, а именно - экспортировать несколько предопределенных функций:
Вы можете проверить другие установленные у Вас ActiveX компоненты - все они имеют вышеназванные функции. Для начала я советую посмотреть на файл - поискать в нём строки "C++", "Delphi", "Borland" etc - можно увидеть много интересного. В нашем файле нашлась строчка "Microsoft Visual C++ Runtime Library" - значит, этот ActiveX написан на Visual C++, и, поскольку не содержит строчек с упоминанием MFC и экспортируемых функций, начинающихся с Afx, скорее всего без использования MFC (почти наверняка на Active Template Library). Запустите oleview.exe - ведь мы хотим знать, какие методы предоставляет нам shotgraph. Чтобы просмотреть библиотеку типов, выберите поддерево "Unclassified Objects", и в нём, среди многих сотен различных объектов, упорядоченных по алфавиту, найдите подраздел, озаглавленный "shotgraph Class". С правой части окна появится несколько вкладок с характеристиками нашего ActiveX'а. Здесь нас интересует так называемый CLSID: строка {B9F7D335-AC62-11D1-8558-0000C050C497} = shotgraph Class. Дело в том, что все интерфейсы идентифицируются по некоему глобально-уникальному ключу. Структура этого ключа находится в файле заголовков BASETYPS.H, и выглядит так: typedef struct _GUID { unsigned long Data1; unsigned short Data2; unsigned short Data3; unsigned char Data4[8]; } GUID; При стандарной записи CLSID первые его восемь символов означают unsigned long Data1 в шестнадцатеричной форме, последующие две пары по четыре символа - соответственно Data2 & Data3, а оставшиеся символы (неизвестно по какой причине первые два байта отделяют от остальных тире) - восемь байт массива Data4. Для чего нам нужен GUID? Дело в том, что GUIDы используются в методе QueryInterface обязательного для всех COM объектов интерфейса IUnknown, так что мы можем узнать по ним, какой именно интерфейс запрашивается, и какой класс программа создаёт для его реализации! Здесь нужно сделать одно важное замечание: для реализации COM объектов не обязательно должен использоваться язык C++. Вы должны просто предоставить указатель на таблицу, состоящую из указателей на функции, реализующие некоторый интерфейс. Но ведь компилятор C++ (как и почти всех объектно-ориентированных языков программирования) создаёт такую таблицу за Вас для представления виртуальных функций некоторого класса. Поэтому все реализации COM объектов пишут на языке C++ в виде классов, наследуемых от абстрактных классов - собственно интерфейсов. Под разделом "shotgrapth Class" есть ещё два раздела: IDispatch & Igifsgr. Как я уже упоминал, интерфейс IDispatch COM объекты должны реализовывать для поддержки OLE Automation. А вот интерфейс Igifsgr является собственно заявленными функциональными возможностями нашего ActiveX компонента. Запишите его GUID: {B9F7D333-AC62-11D1-8558-0000C050C497} - он нам ещё потребуется. Теперь посмотрим, какие функции (методы) составляют этот интерфейс: для этого нажмите на нём правой кнопкой мыши, выберите в контекстном меню пункт View... и нажмите кнопку View Type Info - в правой части появившегося окна и находится информация из библиотеки типов. Она пишется обычно на языке описания интерфейсов - IDL (Interface Definition Language), по синтаксису он напоминает C++ - те же объявления классов и методов классов, только можно добавлять некоторые специфические характеристики классов и их методов, называемых атрибутами. Атрибуты описываются в квадратных скобках: [ uuid(B9F7D333-AC62-11D1-8558-0000C050C497), helpstring("Igifsgr Interface"), dual ] dispinterface Igifsgr { properties: methods: [id(0x00000001), helpstring("method CreateImage")] VARIANT CreateImage( [in] VARIANT* x, [in] VARIANT* y, [in] VARIANT* nc); [id(0x00000002), helpstring("method SetColor")] VARIANT SetColor( [in] VARIANT* ind, [in] VARIANT* r, [in] VARIANT* g, [in] VARIANT* b); ... uuid - это уже знакомый нам GUID идентификатор этого интерфейса. Обратите внимание на ключевое слово dual - оно означает, что в данном случае происходит объединение интерфейсов (в терминах C++ это можно описать как "класс Igifsgr наследует от класса IDispatch (который, как и все COM интерфейсы, в свою очередь наследован от класса IUnknown) "). Для нас же это означает, что в найденной нами таблице указателей на функции первых три метода принадлежат реализации интерфейса IUnknown, следующие четыре - IDispatch, и только с седьмого (считая с нуля) начнутся методы собственно Igifsgr. Далее, в описаниях методов ключевое слово id (номер) значает идентификатор метода в интерфейсе. Для нас он является индексом в таблице указателей на функции на нужный нам метод.1) Итак, запомним id нереализованных методов:
Ну вот, а автор уверял нас нас, что некоторые методы совсем не реализованы... Итак, мы многое узнали из библиотеки типов, Вы можете закрыть OLE/COM Object Viewer - он нам более не понадобится. Наконец, погрузимся в исследование кода ActiveX компонента. Вы всегда должны представлять себе, что именно Вы хотите найти. В данном случае, конечно, можно бы ухватиться за проверку размера картинки, и поискать некоторый код, в котором происходит сравнение с числами 320 (0140h) и 240 (F0h) - однако, это плохой подход. Во-первых, мы не знаем точную инструкцию. Во-вторых, файл достаточно большой (184 Kb), так что на эти поиски Вы можете потратить может и не большую, но точно лучшую часть своей жизни. В-третьх, достоверно неизвестно, что сравнение должно происходить с константами - возможно, они зашифрованы или динамически вычисляются (что одно и то же). Поэтому мы пойдём другим путём. Мы будем искать GUID интерфейса Igifsgr. В IDA Pro нажмите F4, затем Alt + <B>, и наберите образец "B9F7D333". IDA достаточно интеллектуальна, чтобы понять, что её просят найти DWORD, учитывая обратный порядок расположения байт etc. (может, конечно, случиться, что и все GUIDы зашифрованы - в таком случае пришлось бы использовать SoftIce). Образец найден по адресу 100200C: 10021008 shortGraf_CLSID dd 0B9F7D333h 1002100C dw 0AC62h 1002100E dw 11D1h 10021010 db 85h, 58h, 0, 0, 0C0h, 50h 10021010 db 0C4h, 97h Узнаёте? Это GUID интерфейса Igifsgr, я обозвал его поприличнее и настроил размерность данных (понажимайте клавишу D до нужной кондиции). Для массива Data4 нажмите кнопку * (Array) и заполните поле Array Size: 8. Теперь посмотрим, откуда на него ссылаются. В первой же ссылке по адресу 10021190 мы видим кое-что интересное - по адресам выше явно расположена таблица VTBL - таблица указателей на виртуальные методы. Но является ли это таблица VTBL нужного нам класса? Проверить это можно довольно просто - первые три метода в этой таблице принадлежат интерфейсу IUnknown, в котором первый метод QueryInterface предназначен для возврата ссылок на все прочие интерфейсы, реализованные в данном классе. Описание этого метода выглядит так (взято из файла заголовков UNKNWN.H): virtual HRESULT STDMETHODCALLTYPE QueryInterface( /* [in] */ REFIID riid, /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject) = 0; REFIID - указатель на CLSID и GUID. Как видим, эта функция принимает указатель на GUID в качестве входного параметра, и возвращает указатель на указатель на любой интерфейс (или NULL в случае неудачи). Соответственно, где-то в недрах этой функции должны быть манипуляции с GUIDами всех реализованных данным классом интерфейсов. Тело функции 10001C91 push dword ptr [esp+0Ch] 10001C95 push dword ptr [esp+0Ch] 10001C99 push offset off_10021190 10001C9E push dword ptr [esp+10h] 10001CA2 call sub_10017A67 10001CA7 retn 0Ch По адресу 10021190 находится адрес уже знакомого нам GUIDа - это определённо то, что мы искали! Вернёмся к исходной таблице VTBL, теперь мы точно знаем, какой метод как называется - мы можем пронумеровать их на основе имён и индексов методов, полученных при разглядывании библиотеки типов. Сейчас нас интересует метод по индексу [3 + 4 + 0x17 - 1] (BuildPalette) - по адресу 10021104: 100068C3 mov eax, offset loc_10020B4A 100068C8 call __EH_prolog 100068CD sub esp, 858h 100068D3 push ebx 100068D4 xor ebx, ebx 100068D6 cmp IsFull, ebx ; не напоминает ли это 100068DC push esi ; нечто знакомое? 100068DD push edi 100068DE jnz short loc_100068EA 100068E0 mov eax, 80020003h 100068E5 jmp loc_100069B4 ... 100069B4 mov ecx, [ebp-0Ch] 100069B7 pop edi 100069B8 pop esi 100069B9 mov large fs:0, ecx 100069C0 pop ebx 100069C1 leave 100069C2 retn 8 100069C2 BuildPalette endp ; sp = 4 В начале расположена стандартная для C++ инициализация exception frame handlerа, в регистр eax помещается адрес кода очистки стека, которому будет передано управление при возникновении неперехваченного исключения и вызывается функция инициализации нового фрейма обработки исключений. А вот далее идёт некий код, сильно напоминающий проверку "если меня использует плохой парень, пойду-ка я к выходу" (я обозвал эту переменную IsFull). Проверим ещё один метод (скажем, ChangePaletteSize): 10006BA0 ChangePaletteSize proc near 10006BA0 10006BA0 arg_0 = dword ptr 8 10006BA0 arg_4 = dword ptr 0Ch 10006BA0 10006BA0 push esi 10006BA1 mov esi, [esp+arg_0] 10006BA5 cmp dword ptr [esi+368h], 0 10006BAC jz short loc_10006BF3 10006BAE cmp IsFull, 0 10006BB5 jnz short loc_10006BBE 10006BB7 mov eax, 80020003h 10006BBC jmp short loc_10006BF5 ... 10006BF3 loc_10006BF3: 10006BF3 xor eax, eax 10006BF5 10006BF5 loc_10006BF5: 10006BF5 pop esi 10006BF6 retn 8 10006BF6 ChangePaletteSize endp Здесь снова проверяется переменная IsFull и ещё член данного класса по смещению 0x368. Если Вы проверите другие функции данного класса, Вы можете убедиться, что переменная по смещению 0x368 используется в логике работы данного компонента, в частности, она сбрасывется в 0 после вызова функции DeleteObject - скорее всего, это флаг наличия memory (compatible) bitmap - растрового изображения в памяти. А вот переменная IsFull устанавливается в функции по адресу 10001F4A, которая вызывается из функции DllMain. Я не буду давать конкретных рецептов, как вылечить данную конкретную программу - во-первых, я не cracker, во-вторых, данный site занимается не взломом программ, а Reverse Engeneering, в третьих, автора этой программы зовут Mikhail Tchikalov, а ведь мы должны проявлять заботу об отечественном товаропроизводителе. И, наконец, в-четвертых, я дал достаточно информации - если Вам действительно нужен этот ActiveX компонент - Вы сами с ним справитесь. Вместо этого я объясню ещё некоторые тонкости внутреннего строения компонентов ActiveX. Итак, в этом компоненте нам повезло, и мы быстро нашли нужную VTBL. Но ведь это не единственный способ её нахождения. Мы могли бы изучить экспортируемую функцию DllGetClassObject (см. её описание выше). Она должна возвращать указатель на интерфейс IClassFactory, имеющий, кроме обязательных трёх методов интерфейса IUnknown пару своих, среди которых есть метод CreateInstance. Последний описан так (взято из файла заголовков UNKNWN.H): HRESULT CreateInstance( IUnknown * pUnkOuter, // этот аргумент используется для агрегации REFIID riid, // указатель на GUID требуемого интерфейса void ** ppvObject // возвращаемый указатель на интерфейс ); Внимательным анализом этого метода можно достичь многого... Также может помочь отслеживание ссылок на GUIDы прочих предопределённых интерфейсов. Их значения можно посмотреть с помощью oleview.exe в разделе "Interfaces". Некоторые значения перечислены здесь:
Об этих и множестве других OLE интерфейсах Вы сможете прочесть в "OLE Programmers Reference". Можно посоветовать ещё один способ нахождения нужной таблицы VTBL, основывающийся на факте, что фирма Microsoft предоставляет готовую реализацию механизма IDispatch, доступную при вызове функции CreateStdDispatch (находится в файле oleaut32.dll) и описанную так (взято из файла заголовков oleauto.h): HRESULT CreateStdDispatch( IUnknown FAR* punkOuter, void FAR* pvThis, ITypeInfo FAR* ptinfo, IUnknown FAR* FAR* ppunkStdDisp ); Второй параметр представляет собой как раз указатель на VTBL таблицу реализуемого интерфейса. Третий параметр этой функции является указателем на реализацию интерфейса ITypeInfo (интерфейс, используемый для чтения библиотеки типов объекта). Для этого интерфейса Microsoft также имеет готовую реализацию, доступную при вызове функции CreateDispTypeInfo. Она находится в файле oleaut32.dll и определена так (взято из файла заголовков oleauto.h): HRESULT CreateDispTypeInfo( INTERFACEDATA pidata, LCID lcid, ITypeInfo FAR* FAR* pptinfo ); Ещё один приём идентификации методов, участвующих в OLE Automation: большинство методов пользуются в качестве параметров специальным типом переменных Variant. Misrosoft предоставляет несколько функций для манипуляций с этой структурой (находящихся в файле oleaut32.dll): VariantInit, VariantClear, VariantChangeType, VariantI4FromStr и другие. Соответственно, можно найти ссылки на эти функции и с некоторой вероятностью утверждать, что если в некоем методе используется несколько этих функций, то либо этот метод, либо вызвавший его участвуют в механизме OLE Automation. Если какой-либо метод OLE Automation требует в качестве аргумента массива, его можно идентифицировать по вызовам функций, начинающихся с префикса SafeArray. Помните: все строковые переменные, используемые в OLE Automation, являются строками UNICODE, как под Windows NT, так и под Windows 95/98! Если компонент написан на Active Template Library (наиболее эффективно работающие и имеющие наименьший размер), то эта библиотека шаблонов применяет несколько иную реализацию интерфейса IDispatch (исходный код ATL, равно как и MFC, поставляются вместе с Visual C++). ATL использует интерфейс ITypeInfo, который извлекается вызовом метода ITypeLib::GetTypeInfoOfGuid. Сам же интерфейс ITypeLib можно получить при вызове библиотечной функции LoadRegTypeLib, содержащейся в oleaut32.dll. Прототип этой функции таков (взято из файла заголовков oleauto.h): HRESULT LoadRegTypeLib( REFGUID rguid, // GUID библиотеки типов unsigned short wVerMajor, // старшая версия unsigned short wVerMinor, // младшая версия LCID lcid, // код национального языка ITypeLib FAR* FAR* pptlib // указатель на возвращаемое значение ); После столь сложного алгоритма получения интерфейса ITypeInfo используется его метод Invoke, который имеет такой прототип: HRESULT Invoke( VOID FAR* pvInstance, MEMBERID memid, unsigned short wFlags, DISPPARAMS FAR* pDispParams, VARIANT FAR* pVarResult, EXCEPINFO FAR* pExcepInfo, unsigned int FAR* puArgErr ); Из всего множества его аргументов нас интересует только первый - pvInstance; это как раз и есть искомая таблица VTBL реализации интерфейса, чья библиотека типов была загружена в вызове LoadRegTypeLib. Если же библиотека типов находится в отдельном файле, для её извлечения может применяться другая функция - LoadTypeLib, также расположенная в oleaut32.dll Как видите, исследование компонентов ActiveX, при наличии достаточных знаний, не представляет собою сложной задачи - мне даже не потребовалось запускать SoftIce. Reverse Engeneer'ы должны просто поблагодарить замечательную фирму Microsoft за изобретение и проталкивание столь легко идентифицируемого и незащищённого двоичного формата. Несколько советов авторам ActiveXЯ, вроде бы как, сам отношусь к авторам, так что могу позволить себе давать советы. Помнится, на fravia было несколько дельных статей, касающихся общей защиты программ, но ведь их почти никто и никогда не читает, поэтому я кое-что повторю и оттуда тоже.
А главное - помните: развитию crackerов способствует низкий уровень жизни. Я не плохой парень, но я не могу позволить себе при среднемесячной зарплате чуть более 100 US $ покупать программы, стоящие немногим менее этой суммы. Таким образом, если, предположим, программа стоит 1000$, я могу позволить себе затратить на её взлом 1000 / 100 = 10 месяцев, т.е. если я взломаю её за меньшее время, это будет экономически целесообразно (мне сложно представить себе программу, которая не отдалась бы мне за десять месяцев...)! Поэтому бороться с crackingом иными мерами, кроме экономических, невозможно. На этой печальной ноте позвольте откланяться. Список литературы и on-line ресурсов
|