![]() |
Одним из приоритетных направлений в разработке любого продукта является его отладка. Ведь мало написать удовлетворяющий требованиям исправно работающий код, так нужно ещё и протестить его в "токсических условиях", на предмет выявления всевозможных ошибок. Для этих целей, инженеры Microsoft включили в состав ОС полноценный механизм дебага в виде двух библиотек пользовательского режима: это Dbgeng.dll – основной движок отладки (Debug Engine), и Dbghelp.dll – вспомогательный процессор отладочных символов PDB (Program Database).
На моей Win-7, библиотека символов имеет размер 0.8 Мб и выдаёт на экспорт аж 205 стандартных API-функций, а вот вторая Dbgeng.dll в три раза тяжелее своего (со)брата с размером ~2.5 Мб, зато экспортирует с выхлопной трубы всего 3 функции. От сюда следует, что эта либа от нас явно что-то скрывает, поскольку жалкие три процедуры никак не могут весить более двух мегабайт. В данной статье мы попытаемся заглянуть внутрь отладочного движка Engine, и в качестве примера вытащим из него полноценный дизассемблер инструкций процессоров х86. Оглавление: 1. Знакомство с системным механизмом отладки; 2. Component-Object-Model в ассемблере; 3. Структура СОМ-библиотеки Dbgeng.dll; 4. Практика – пишем дизассемблер; 5. Заключение. --------------------------------------------------------------- 1. Знакомство с механизмом отладки Первые библиотеки отладки, так-же известные как файлы "Symbolic Debugger Engine", были созданы компанией Microsoft в 2001-году для операционной системы Windows-XP. Большая часть либы Dbghelp.dll содержит в себе функции с префиксом(Sym), что говорит об их принадлежности к символьному процессору. Они позволяют по указанному адресу вычислять имена функций, определять типы данных, а также номер строки и название файла, в котором эта строка находится. Поддерживаются и обратные операции, например поиск адреса функции по её имени. Это достаточно творческая единица, если знать как ею пользоваться (см.документациюна сайте мелкософт). В состав этой библиотеки входят и привычные нам функции, без каких-либо префиксов. Например потянув за всего одну EnumerateLoadedModules() можно получить список всех модулей DLL (вместе с виртуальной базой и размером), которые загружены в интересующее нас приложение. Поскольку вся черновая работа происходит в фоне, то в большинстве случаях это удобно. На входе, функция требует лишь дескриптор процесса (в примере ниже я передаю -1, т.е. текущий процесс), и адрес callback-процедуры, куда она в цикле будет сбрасывать информацию о модулях. Функция связана со-своей "обратной процедурой" невидимой нитью, так-что программный цикл выстраивать не нужно – обход на автомате прекращается, как только коллбэк возвращает родителю ошибку: C-подобный: Код:
format pe consoleВсё идёт прекрасно до тех пор, пока мы не сталкиваемся с вызовом функций из основного движка-отладки Dbgeng.dll – здесь и начинается самое интересное. Эта библиотека построена по модели СОМ (Component-Object-Model), а значит и вызывать из неё функции нужно соответствующим образом. Но проблема в том, что в отличии от крестов С++ и прочих высокоуровневых языков, ни один из ассемблеров не поддерживает на данный момент технологию COM/ActiveX, и врядли уже будет поддерживать в будущем. Ассемблер – это язык низкого уровня, а прослойка СОМ находится в иерархии намного выше. Как уже упоминалось, библиотека Dbgeng.dll выдаёт на экспорт всего 3-функции (см.в тотале по ctrl+q) – это DebugCreate(), DebugConnect() и DebugConnectWide(). Но под капотом у неё припрятаны ещё порядка 300 внутренних, неэкспортируемых обычным способом функций. Чтобы подобраться к ним, для начала нужно разобраться, что вообще такое COM-интерфейс и как его реализуют современные компиляторы – вот об этом и поговорим.. 2.Component-Object-Modelв ассемблере COM – многокомпонентная, клиент-серверная модель объектов Microsoft, которая является продолжением OLE и фундаментальной основой многих других технологий, в том числе ActiveX и DCOM (Distributed COM, работа с сетью). Ключевым аспектом COM является то, что эта технология обеспечивает связь между клиентом (нашим приложением) и сервером (операционной системой) посредством "интерфейсов". Именно интерфейс предоставляет клиенту способ узнать у сервера, какие конкретно возможности он поддерживает на текущий момент. В терминологии языка С++ интерфейс – это абстрактный базовый класс, все методы которого являются виртуальными. То-есть вызов этих методов осуществляется через специальную таблицу-указателей, известную как vTable. Например, вызов метода Код:
QueryInterfaceКод:
IUnknownКод:
IUnknown::QueryInterface()Если-же посмотреть на СОМ глазами ассемблера, то интерфейс представляет собой ничто-иное, как обычную структуру в памяти. Чтобы придерживаться общих правил, мы будем называть её так-же, т.е. "vTable". В свою очередь методы – это лишь иное название уже привычных нам API-функций, а указатели на эти функции хранятся внутри интерфейса. Таким образом, интерфейс можно рассматривать как массив указателей на СОМ-функции. Влиться в эту тему поможет ветка наwasm.in, где представлены материалы по СОМ с реальными примерами на ассемблере – "допризывникам" настоятельно рекомендуется к прочтению. В операционной системе Win имеется огромное количество СОМ-интерфейсов и это не удивительно, ведь Microsoft строит системы используя объектно-ориентированный подход программирования, а частью ООП является как-раз-таки OLE/ActiveX/СОМ. Чтобы из этого общего пула у нас была возможность выбрать и использовать в своих программах конкретный интерфейс, система назначает ему уникальный идентификатор GUID. Собрав в единую базу, Win хранит все эти идентификаторы в своём кусте реестра под названием "HKEY_CLASSES_ROOT\Interface". Если выбрать любой GUID в левом окне, то в правом получим отождествлённое с этим идентификатором, название интерфейса: https://forum.antichat.xyz/attachmen...c1af2a2c32.png СОМ-сервер операционной системы имеет базовый интерфейс под названием "IUnknown". Он глобален и все остальные наследуются именно от него. GUID этого интерфейса имеет значение Код:
{00000000-0000-0000-C000-000000000046}C-подобный: Код:
struct IUnknownНапример, когда мы получаем от сервера ссылку (указатель) на какой-нибудь интерфейс, его метод AddRef() на автомате увеличивает внутренний счётчик-обращений к данному интерфейсу. Если-же интерфейс нам больше не нужен, мы должны вызвать его метод Release(), который соответственно уменьшит этот счётчик на 1. Сервер периодически парсит счётчики активных интерфейсов и если обнаруживает в нём нуль, то из-за ненадобности сразу выгружает его из памяти. Так реализуется "время жизни" СОМ-интерфейсов, и это стандартная схема учёта системных структур, в памяти Win. 3. Структура СОМ-библиотекиDbgeng.dll Будем считать, что прошлись по макушкам СОМ-технологии, и теперь рассмотрим её реализацию внутри главного героя этой статьи – библиотеки Dbgeng.dll. В каком-то смысле, эта библиотека сама является полноценным СОМ-сервером, поскольку GUID'ы её интерфейсов не прописаны в системном реестре Win, хотя библиотека и является детищем самой Microsoft. Из этических соображений, все разработчики СОМ-интерфейсов обязаны сопровождать свой продукт полной документацией, чтобы армия прикладных программистов могла использовать незнакомые интерфейсы в своих программах. Связано это с тем, что не зная GUID мы просто не сможем найти ни один интерфейс в системе, и соответственно лишимся возможности вызывать из него методы. Движок-отладки Dbgeng.dll отлично документирован в репозитории мягких – общие сведения о нём можно почерпнутьпо этому линку. Что касается описания непосредственно имеющихся в наличии методов и GUID всех интерфейсов, то они находятся в заголовочном файле Dbgeng.h, электронная версия котороголежит здесь. Судя по этому хидеру, в данную библиотеку включён не один, а целая дюжина связанных с отладкой различных интерфейсов, и в каждом из них имеются свои функции (методы). Исторически, 16-байтные GUID интерфейсов принято обозначать как IID, что подразумевает "Interface-Identifier". Одной из примечательных особенностей СОМ-интерфейсов является их масштабируемость. Так, если мы захотим изменить уже существующий интерфейс, то достаточно написать недостающие методы, и добавить указатели на них в конец прежнего интерфейса. К примеру, каждый из представленных выше 13-ти фейсов имеет дополнительные экземпляры, к именам которых добавляется порядковый номер по типу: IDebugClient (основной интерфейс), и дальше IDebugClient2 (3,4,5,6,7). Каждый последующий экземпляр включает в себя какие-то свежие методы и ему назначается новый GUID, в результате чего интерфейс шагает в ногу со-временем. Если учитывать все интерфейсы вместе с расширенными, то в библиотеке Dbgeng.dll операционной системы Win7 зарегистрировано всего 35 СОМ-интерфейсов, а общее число методов в них приближается к отметке 300. Забегая вперёд скажу, что не все они реализованы на должном уровне, в чём мы убедимся позже. Здесь нужно отметить, что движок-отладки включённый в состав ОС отличается от движка ядерного отладчика "WinDbg" – у системного версия [6.1.7601], а у того, что использует отладчик [6.12.2]. Системный файл плохо зарекомендовал себя тем, что в нём вырезана поддержка удалённой отладки, что является козырем отладочного ядра WinDbg. Поэтому и размеры библиотек у них разные, о чём свидетельствует скрин ниже: https://forum.antichat.xyz/attachmen...1a05ac2145.png Посмотрим на рисунок ниже, где представлена обобщённая структура библиотеки Dbgeng.dll. Чтобы воспользоваться услугами сервера-отладки, мы должны сначала активировать его функцией CoInitialize() из библиотеки подсистемы исполнения OLE32.dll. Теперь нужно создать "клиента отладки" функцией DebugCreate() из либы Dbgeng.dll, передав ей в виде аргумента GUID интерфейса "IDebugClient::". Это основной интерфейс клиента, где собраны часто используемые им (т.е. нашим приложением) методы. Если зайти отладчиком OllyDbg в функцию DebugCreate() по [F7], то можно обнаружить, что она проделывает массу полезной работы – например копирует из тушки движка в пространство пользователя различные структуры, находит через GetProcAddress() и подключает вспомогательные функции отладки из библиотеки Ntdll.dll типа: DbgEvent(), DbgBreakPoint() и многое другое. Именно эта функция создаёт полный контекст отладки в памяти ОЗУ, и нам остаётся лишь вызывать методы из требуемых СОМ-интерфейсов: https://forum.antichat.xyz/attachmen...c019096406.png В качестве демонстрационного примера для вводной части, предлагаю код ниже, который в цикле будет запрашивать у базового интерфейса "IUnknown::" все имеющиеся в наличии интерфейсы отладочного движка. Как уже упоминалось, всего в библиотеке Dbgeng.dll их зарегистрировано 35-штук (вместе с расширенными), а GUID'ы этих интерфейсов я вынес во-внешний инклуд (см.скрепку в конце статьи). По сути здесь нет ничего особенного, однако следующий нюанс требует некоторого пояснения.. Значит передаём функции DebugCreate() GUID интерфейса "IDebugClient::", на что функция возвращает нам адрес этого интерфейса в памяти. Если вернуться к рис.выше, то можно обнаружить, что первые три метода в любом интерфейсе, есть копия базового интерфейса "IUnknown::", а первый метод – как-раз нужный нам QueryInterface(). Он ожидает на входе два аргумента – это GUID искомого интерфейса, и указатель на переменную, куда метод сохранит его адрес. Особое внимание нужно обратить на способ вызова СОМ-методов в ассемблере. Дело в том, что помимо обозначенных прототипом аргументов, мы всегда должны добавлять ещё один лишний аргумент – в спецификации его назвали "This" и представляет он собой адрес интерфейса. Другими словами, перед вызовом любого метода из какого-либо интерфейса, мы должны явно указать серверу, из какого именно осуществляем вызов. Этот аргумент(This) всегда является первым аргументом метода – вот пример: C-подобный: Код:
format pe consoleЗдесь я добавил некоторую вспомогательную информацию в шапке, чтобы продемонстрировать расположение интерфейсов и их методов. Так, первые две строчки указывают на наше пользовательское пространство памяти, куда функция DebugCreate() любезно сбросила указатели на интерфейсы. А вот сами методы находятся уже внутри библиотеки Dbgeng.dll, о чём свидетельствует их адреса с базой Код:
0x5D0D0000Код:
EAX=0Код:
EAXC-подобный: Код:
;5. Заключение. Программирование СОМ-интерфейсов открывает перед нами огромные возможности, поскольку в своих/больших штанинах они прячут достаточно интересные методы, подобраться к которым можно только через указатель на интерфейс, аля GUID. По модели СОМ построена добрая половина системных библиотек – объектная модель позволяет нам работать с такими механизмами как WMI (инструментарий Windows), технологией DirectX, с библиотекой Shell32.dll и многое другое. Как упоминалось выше, любой СОМ-интерфейс обязан быть документированным, поэтому проблем не возникает – главное уловить логическую нить, а дальше уже дело техники. По уже отработанной схеме, в скрепку ложу два исполняемых файла, а так-же инклуд с описанием GUID'ов всех интерфейсов движка Dbgeng.dll, с полным описанием входящих в их состав методов. Всем удачи, пока! |
Респект и уважуха за такой труд
|
|
Marylin
, я тебя иногда почитываю. возникла идея все у тебя сплагиатить. то есть не сделать ничего нового, просто объединить оба твоих примера и переиначить их. что (будет) делает прога: с ней идет инифайл с указанием целевой библиотеки (COM-сервера), с указанием процедуры загрузки интерфейсов из библиотеки, и текстовый файлик с расписанными интерфейсами. теперь собственно что прога будет делать: загружать интерфейс по алгоритму из инифайла, затем осуществлять перебор по методам интерфейса, если будет встречен метод с кодом Код: Код:
mov eax, E_NOTIMPLа на все прочие методы натравливать вышеописанный дизассемблер (грубо - до первой встреченной инструкции ret ( т.е. инструкции в потоке инструкций начинающейся с байта $C2 или $C3)), в результате программа будет показывать доступность интерфейсов, реализованность методов (при наследовании реализованность методов может как появляться так и исчезать, так и быть реализованной иначе), дизассемблинг методов, и это будет конфигурируемо - можно натравливать на любой COM-сервер. У меня DBGENG.INC это один из исходников будущей программы, ее же синтаксис планируется для перебора интерфейсов и их методов, просчет числа параметров методов так же из этого файла. в 2х словах - идея программы - ГУИ, 2 компонента - дерево и едитбокс, в дереве интерфейсы и их методы, в эдитбоксе информация синхронно дерева - адреса доступности интерфейсов, реализованность метода, дизасм метода. все остальное файлы рядом с программой 1 ini и 1 inc. (На первом этапе наверно без 1 ini и 1 inc, а жестко закодировать перечень интерфейсов и их методов из dbgeng.dll) |
Цитата:
|
Не могу найти, как получить длину инструкции; хотел заюзать эти СОМ для простого дизассемблера длин (чтобы не тащить сторонние). Но что-то не найду ничего среди методов - возвращают или строку или еще что.
А так - статья шикарная , как всегда! В мсдн не указано (или я не видел), что обязательно нужно WaitForEvent вызывать перед дизасмом, иначе возвращает ошибки. В теории, есть метод GetNearInstruction, на практике он иногда возвращает 0, неясно что такое 0 - длина инструкции не может же быть нулевая; IDebugControl::GetNearInstruction (dbgeng.h) - Windows drivers , кто лучше шарит в инглише, мб будут какие-то мысли? Написать функцию, на вход адрес , на выходе должна быть длина инструкции. |
| Время: 06:37 |