На чём ты написана, радость моя?

© 2006 Вадим Станкевич,
Компьютерные Вести

Часть первая. Шпионим за оконными классами.

Скажите, пожалуйста, у Вас никогда не возникало желания в ресторане узнать рецепт сильно понравившегося Вам блюда? Спрашивали? И что в ответ? "Ах, нельзя? Какая жалость! Ну тогда хотя бы скажите ингредиенты... Что, серьёзно, только лук, картошка и майонез?! Не может быть..."

Когда видишь хорошо, и даже не просто хорошо, великолепно сделанную программу, тоже возникает желание узнать, как именно она разработана. И если с open-source программами это не составляет никаких трудностей, то сделать это в случае коммерческих разработок – большая проблема. Впрочем, каждый программист знает, что существуют такие вещи, как отладчики и дизассемблеры. С их помощью любую программу, написанную для платформы Windows, можно представить в виде ассемблерных кодов. Действие это в отношении чужих программ не приветствуется, и даже, скажем прямо, является наказуемым в соответствии с международным законодательством. Хотя разве это остановит одержимого любопытством исследователя?.. Да и не любую программу получится так "разложить по базису": если написана она, например, на Java, то обычным Windows-дизассемблером её не возьмёшь. То же касается и тех программ, которые созданы для платформы Microsoft .NET Framework.

Впрочем, не всегда есть смысл исследовать программу подробно, с дизассемблированием. Иногда просто интересно узнать, с помощью какого средства она разработана. И именно этим мы сегодня с вами и займёмся – будем пробовать узнавать, на чём написана та или иная программа. Способов определить это, не нарушая закон, существует великое множество.

Сегодня мы рассмотрим только один способ распознавания инструмента, с помощью которого создано приложение. Только один только из-за ограничений в объёме, свойственном газетным статьям. Я назвал бы его "Шпионим за оконными классами". Он, конечно, не идеален, поскольку применим только к тем программам, которые имеют стандартный пользовательский интерфейс Windows. Если программа запускается в консольном режиме (т.е. не имеет графического интерфейса как такового), то этот способ не сработает. Если интерфейс программы оформляется с помощью т. н. "скинов", то способ тоже работает не всегда.

Каждый элемент управления, будь то окно, кнопка, панель инструментов или что-либо ещё, при создании регистрируется в системе. При этом он получает уникальный дескриптор (handle), который нужен для того, чтобы программа могла точно установить, с каким именно элементом управления пользователь в данный момент взаимодействует. При регистрации окна (а с точки зрения системы кнопка такое же окно, как и то окно, на котором она расположена) всегда указывается название оконного класса этого элемента управления. Это указывается именно для того, чтобы отличать кнопку от панели задач, а панель задач – от строки поиска в Internet Explorer. При этом, как известно, большинство программ используют интерфейс, созданный с помощью специализированных библиотек. Таких библиотек много – VCL, MFC, Windows Forms... Каждая из них имеет свои особенности именования таких классов. Именно этим свойством графического интерфейса Windows-приложений мы и воспользуемся.

Итак, приступим к исследованиям. Во-первых, нам нужен какой-нибудь инструмент для того, чтобы узнавать имена оконных классов тех или иных элементов управления. Такие инструменты есть, и их существует не так уж и мало, однако я укажу лишь два, от самых популярных производителей – Microsoft Spy++, входящий в состав Visual Studio, и Borland WinSight, входящий в поставку Delphi и C++ Builder. Лично я работал именно с WinSight (его можно найти в папке DELPHI\BIN\WS32.EXE), и, соответственно, рассказ будет именно о нём. Впрочем, Spy++ работает аналогично, и чтобы разобраться с ним, достаточно лишь некоторое время внимательно почитать справку.

Посмотрите на скриншот, где виден внешний вид WinSight. Верхняя половина окна этой утилиты – дерево оконных элементов управления, зарегистрированных на текущий момент в системе. Там отображаются все окна, а не только те, которые видны пользователю, в том числе все системные. Поэтому, собственно говоря, их там и так много. Нижняя половина – очередь сообщений Windows, которые относятся к выделенному в верхней половине окну, впрочем, сейчас нас нижняя половина не слишком интересует. В связи с обилием окон в дереве возникает проблема: как узнать в списке именно то окно, которое нас в данный момент интересует? Делать вручную это не всегда просто, поэтому лучше воспользоваться встроенными в WinSight возможностями. Для этого в меню "Spy" нужно выбрать (отметить галочкой) пункт "Follow Focus". Теперь, когда он отмечен, достаточно сделать активным на экране нужное окно, а после переключиться на WinSight по комбинации клавиш Alt+Tab. WinSight самостоятельно выделит в дереве нужное окно, так что нам остаётся лишь исследовать его.

Информация об окне в WinSight представлена в следующем виде: Tree Handle {Class} Module Position "Title". Tree – это идентификатор типа окна: Icon и Overlapped – главное окно, Child и Popup – ему подчинённые. Handle – это тот самый уникальный дескриптор, который получает оконный элемент при регистрации в системе. Он представлен в виде числа в шестнадцатеричной форме записи (например, 004201DA). Class, который записан в фигурных скобочках, это и есть тот самый оконный класс, ради которого и делается вся это катавасия. Module – имя исполняемого файла, к которому относится данное окно, Position – экранные координаты окна, а Title – заголовок окна. Но нас, как я уже говорил, интересует сейчас в первую очередь атрибут Class.

Теперь давайте возьмём несколько программ и поизучаем их с помощью WinSight. Лично я выбрал несколько, заведомо написанных с помощью разных библиотек – Total Commander, NUnit, Microsoft HTML Help Workshop, WinRAR, QuickHTML Suite и Hexapad.

Главное окно Total Commander имеет класс TTOTAL_CMD, а названия классов всех дочерних окон начинаются с префикса "T" (TDrivePanel, TButtonBar, TMyPanel, TComboBox). Как написано в справке к Total Commander, "Total Commander разработан с использованием Borland Delphi 1.0 (16 бит) и 2.0 (32 бит)". То есть программы, разработанные с использование библиотеки Visual Components Library (VCL), применяемой в Delphi, имеют в названиях оконных классов префикс "T". К сожалению, по одному этому признаку идентифицировать наверняка язык разработки не удастся, так как эта библиотека используется и при разработке приложений в Borland C++ Builder. Программисты, использующие VCL, наверняка обратили внимание на то, что название оконного класса совпадает с названием класса компонента в IDE.

Теперь давайте посмотрим на Hexapad, разработанный также с использованием Delphi. Только вместо VCL разработчиком применялась KOL – альтернативная объектная библиотека. Как видно, названия всех оконных классов начинаются с "obj_". Поскольку версии KOL для C++ Builder не существует, можно с уверенностью утверждать при виде приложения, оконные классы элементов которого начинаются с "obj_", что оно написано на Delphi.

А сейчас, уважаемые, мы посмотрим в сторону WinRAR. Оконные классы элементов управления этой программы имеют следующие имена: WinRarWindow, ComboBoxEx32, ComboBox, Edit, ToolbarWindow32, msctls_statusbar32, SysListView32, SysHeader32 и др. Те, кто когда-либо работал с диалогами в виде ресурсов, знает, что большинство имён этих классов в точности соответствуют определённым в системе классам. Поскольку программисту на Delphi или C++ Builder обычно нет необходимости использовать напрямую Windows API, то можно с определённой долей уверенности считать, что эта программа написана на языке C++ без использования каких-либо сторонних библиотек (или, что менее вероятно, на Delphi, но без VCL). Впрочем, как показывает исследование HTML Help Workshop, интерфейс которого создан при помощи Microsoft Foundation Classes (MFC), такие же имена у классов будут при использовании этой библиотеки. Впрочем, для приложений использующих MFC, и написанных, соответственно, с помощью Microsoft Visual C++, характерно наличие оконных классов, названия которых начинается с "Afx". Хотя их может и не быть – например, в том же HTML Help Workshop таких классов намного меньше тех, что имеют стандартные имена. И ещё стоит отметить, что MFC могут использовать и разработчики, работающие с другими компиляторами C++ и в других средах разработки.

Так что в общем случае при встрече с подобными именами классов (вроде msctls_statusbar32, SysListView32, BUTTON, EDIT и т. д.), нам становится ясно, что... ничего, собственно, и не ясно.

Немного лучше обстоят дела с приложениями для .NET Framework. Я специально выбрал для рассмотрения NUnit, который точно написан на C# и поставляется с исходными текстами. Кстати, именно его оконные классы в основном и попали на скриншот. Как видите, они имеют вид "WindowsForms10.XXXXXXX". Часть после точки может быть любой, но это начало – "WindowsForms10" отличительный признак .NET-приложений. Впрочем, вопрос о языке, с помощью которого разработано это приложение, всё равно остаётся открытым.

QuickHTML Suite написана на Visual Basic 6-й версии. Соответственно WinSight показывает множество оконных классов данной программы, в имени которых содержится набор символов "RT6". Этот набор символов и можно считать опознавательным для программ, созданных с помощью Visual Basic 6.

Вот так обстоят дела. Думаю, немного "поковырявшись" с другими средами разработки, можно выяснить, какой отпечаток накладывают они на оконные классы создаваемых с их помощью приложений. Однако, как видите, этот метод исследования программ имеет много минусов, среди которых – низкая точность в отношении программ, написанных на "голом" WinAPI или с использованием Microsoft Foundation Classes. Поэтому в следующий раз я расскажу про другие способы исследования программ с целью выяснить, на чём они были написаны.

Часть вторая. "Почерк" компилятора.

В первой части статьи мы с вами, уважаемые читатели, пытались выяснить, на чём написана та или иная программа, анализируя названия оконных классов её элементов управления. И пришли к выводу, что такой анализ не отличается высокой точностью результатов. Кроме того, оставался невыясненным вопрос о консольных программах, DLL-файлах и ActiveX-элементах (OCX). Поэтому сегодня мы продолжим исследовать исполняемые файлы на предмет выяснения, каким именно средством разработки пользовались программисты при их создании.

Как известно, все программы для Windows существуют в виде EXE или DLL-файлов. Все другие типы (те же самые OCX, например) представляют собой особую разновидность одного из этих двух. Любой 32-битный исполняемый файл в этой операционной системе имеет формат PE, что значит Portable Executable. Portable ("Portable" - переносимый (англ.)) он в том смысле, что будет запускаться на любом "железе", на котором согласится работать Windows. Внутри PE-файла находятся не только команды, которые система передаёт процессору, но и самая разная служебная информация: используемые DLL-библиотеки, содержащиеся внутри COM и ActiveX-объекты, информация о версии, различные ресурсы – картинки, значки, таблицы строк и прочие используемые программой данные, называемые в Windows ресурсами. Кроме того, среди служебной информации записывается версия компоновщика (программы, которая осуществляет компоновку машинных кодов в формат PE) и некоторые другие сведения, которые мы сможем использовать в нашем исследовании.

Как и в первом исследовании, нам понадобятся специализированные инструменты. Их в Интернете встречается немало, но, просмотрев некоторые из них, я пришёл к заключению, что большинство мало подходят для целей, которые мы преследуем в этой статье. А наиболее подходящим из них оказался FileInfo – плагин к Total Commander. Надеюсь, любители Far и других файловых менеджеров не обидятся?

Работать с FileInfo очень легко – достаточно скачать его с сайта автора (размер совсем невелик, несложно будет скачать её даже тем, у кого Dial-Up) и установить. Теперь, выделив в окне Total Commander какой-нибудь исполняемый файл, нажмите F3, и перед Вами появится точно такое окно Lister’а, как то, которое Вы видите на скриншоте к статье. Первая вкладка – информация о версии файла, вторая – служебная информация, третья и четвёртая – дерево библиотек и список используемых функций каждой библиотеки соответственно. Последние две вкладки сейчас не так интересны. В общем-то, функциональность третьей и четвёртой вкладок можно легко заменить утилитой Depends из Microsoft Visual Studio, но я всё же рекомендую скачать и установить FileInfo, поскольку в нашем сегодняшнем исследовании это – наш практически идеальный помощник.

Хотя список динамически подключаемых библиотек (DLL), используемых большинством Windows-приложений, практически идентичен, некоторые нюансы всё же присутствуют. Причём в ряде случаев эти самые нюансы могут сразу выдать компилятор, породивший данную программу. Например, если в дереве используемых библиотек (третья вкладка окна FileInfo) встретится MSVBVM60.DLL, знайте, что эта программа написана на Visual Basic 6. Соответственно, для пятой и четвёртой версий этой среды разработки названия библиотек будут MSVBVM50.DLL и MSVBVM40.DLL. Как известно, программы, написанные на Visual Basic, нуждаются в наличии этой DLL-библиотеки, так как они преобразуются компилятором не в машинный код непосредственно, а в некоторый промежуточный псевдокод (так называемый P-Code), который и выполняет виртуальная машина, содержащаяся в этой DLL'ке.

Кстати, Visual Basic 4, 5 и 6 – не единственные компиляторы, которых выдаёт список библиотек, использованных программой. Если в списке встретится MFCxx.DLL, где xx – любые цифры, обозначающие номер версии, знайте, что эта программа написана на Microsoft Visual C++. Этот же компилятор (и, соответственно, одноименную среду разработки) выдаёт присутствие библиотеки MSVCRT.DLL. Впрочем, если с первой библиотекой всё однозначно, нигде, кроме Visual C++, MFC в виде DLL не используется, то MSVCRT.DLL может использоваться и программами, скомпилированными с помощью MinGW Compiler System. Впрочем, язык (C++) всё равно определяется по присутствию этой библиотеки однозначно.

Программы для Microsoft .NET Framework содержат в списке используемых библиотек одну-единственную MSCOREE.DLL, однако, каким образом определить, что за компилятор эту программу сгенерировал, я пока что не придумал.

Исследуя EXE-файлы, Вы, может быть, заметили, что они, в отличие от DLL, как правило, не экспортируют никаких функций, т.е. список внизу окна при активной четвёртой вкладке FileInfo остаётся пустым. Так бывает обычно, но далеко не всегда. Сами программисты практически никогда не добавляют экспортируемые функции в EXE-файл, т.к. знают, что система сама не позволит обратиться к ним из другого приложения – для экспорта существуют DLL-библиотеки. Однако есть компилятор, который всегда добавляет в собираемую программу таблицу экспорта. Это Borland C++. Посмотрите на скриншот (или откройте в FileInfo файл WinRAR.EXE). В списке экспортируемых функций находится две _CPPDebugHook и _GetExceptDLLInfo. Первую функцию Borland C++ добавляет всегда и во все файлы. В более старых версиях компилятора она называется _DebuggerHookData. Вторая встречается не во всех файлах, созданных с помощью Borland C++, а только начиная с 5-й версии этого компилятора. Причём, как Вы, может быть, помните, исследуя оконные классы WinRAR, мы пришли к выводу, что интерфейс его написан на MFC или с использованием "чистого" Windows API. Список экспортируемых функций это подтверждает. Дело в том, что для программ, созданных с использованием VCL, количество экспортируемых функций нередко составляет несколько тысяч. Просмотрев их имена, можно сделать заключение, что компилятор добавляет методы всех используемых классов и компонентов VCL. Происходит это, по всей видимости, вследствие совместной работы компиляторов языков Delphi (Object Pascal) и C++ при разработке программ с использованием VCL в среде Borland C++ Builder.

Сам Delphi обнаружить сложнее, поскольку никаких автоматически экспортируемых функций компилятор не добавляет и никакими дополнительными DLL-библиотеками программу не нагружает. Можно, конечно, установить, что написана программа именно на Delphi, комбинируя исследование оконных классов с изучением списка экспортируемых функций, однако можно не звать на помощь WinSight или Spy++, обойдясь только FileInfo. Для этого нужно перейти в окне FileInfo на вторую от начала вкладку и изучить содержимое поля Time Date Stamp в группе “FILE HEADER” (выделено тёмным на скриншоте). Проверено, что для всех программ, написанных на Delphi версий со второй по десятую значение этого поля равно "2A425E19h -> 20/06/1992 01:22:17". Осуществить контрольную проверку можно по значению поля Linker Version из группы OPTIONAL HEADER: для программ, написанных на Delphi, оно всегда равно 2.25.

К сожалению или к счастью, но набором Visual Basic, Visual C++, Borland Delphi и Borland C++ многообразие современных компиляторов не ограничивается. Хотя для Windows это самые распространённые компиляторы, вполне можно столкнуться и с тем, что заинтересовавшая Вас программа создана с использованием какого-нибудь менее популярного в наши дни средства. Например, замечательный архиватор WinIMP, который умеет неплохо сжать даже GIF-файлы, создан с использованием Watcom C++ (кстати, о компиляторах Open Watcom можно прочесть в №9 (560) "Компьютерных вестей" за 2 марта 2006 года). Как я об этом узнал? Очень просто. В меню "Вид" ("View") Lister’а выберите пункт "Двоичный" ("Binary"). Можно с тем же успехом просто открыть исполняемый файл в обычном блокноте Windows. Среди скопления маловразумительных символов в начале файла можно разобрать фразу "this is a Windows NT windowed executable". Кстати, подобная фраза присутствует во всех PE-файлах, с вариациями: "This program cannot be run in DOS mode"; "This program must be run under Win32" или "This program requires Microsoft Windows". Это тот текст, который увидит на экране пользователь DOS, запустивший Windows-программу. Первый вариант "приветственной" фразы стандартен для компиляторов Microsoft и GNU, второй – для компиляторов производства Borland, третий характерен для 16-битных приложений, т.е. программ для Windows 3.1. Компиляторы Watcom пишут либо приведенную вначале фразу, либо "this is a Windows NT dynamic link library", либо "this is a Windows NT character-mode executable". Впрочем, вопреки заверениям компилятора, программы превосходно запускаются не только из-под NT, но и из-под систем семейства Windows 9x.

Если и теперь не удалось идентифицировать компилятор, собравший файл, который не даёт Вам покоя, то ещё не всё потеряно. В двоичном режиме Lister’а или в блокноте можно поискать внутри файла название компилятора. Большинство компиляторов не забывает вставить информацию о себе, любимом, внутрь создаваемого исполняемого файла. Например, забив в строке поиска “Borland C++” для файла WinRAR.EXE, обязательно найдём строку "Borland C++ - Copyright 1999 Inprise Corporation". Компилятор Free Pascal добавляет в исполняемый файл строку вида "FPC 2.0.1 [2005/10/02] for i386 - Win32", где вместо 2.0.1 может стоять любая другая версия компилятора. Однако из этого правила исключением являются GNU C++ (MinGW) и Microsoft Visual C++. Вполне вероятно, что программа, в которой не содержится информации об использованном для её создания компиляторе, создана с использованием одного из этих двух.

Что ж, на этом об исследовании программ, пожалуй, всё. Могу ещё добавить, что каждый компилятор имеет свой "почерк" и в генерируемом ассемблерном коде, так что распознать, на чём написана программа, можно и путём дизассемблирования её. Но этот способ не приветствуется законом, да и расточительно это – дизассемблировать программу и исследовать её ассемблерный код только для того, чтобы узнать, на чём она написана. Если, прочитав эту статью (включая первую часть), Вы так и не смогли найти ответа на вопрос, на чём написана интересующая Вас программа, попробуйте поискать ответа в справке к программе или, если и там ответ отсутствует, задать этот вопрос её производителю (например, по электронной почте). Обычно разработчики не видят в таких вопросах ничего предосудительного и честно на них отвечают. Кроме того, если Вас заинтересовала какая-то программа, распространяющаяся по одной из open-source лицензий, т.е. исходные тексты этой программы открыты, часто проще будет скачать исходный текст, чем исследовать её с помощью описанных мною методов.

© 2006 Вадим Станкевич, опубликована в газете Компьютерные Вести


Пожалуйста, оцените статью
Отлично
Хорошо
Средне
Плохо
Очень плохо