Если первого встречного программиста спросить, с какой функции начинается выполнение Windows-программы, вероятнее всего, мы услышим в ответ — «с WinMain». И это будет ошибкой. На самом деле первым управление получает стартовый код, скрыто вставляемый компилятором. Выполнив необходимые инициализационные процедуры, в какой-то момент он вызывает WinMain, а после ее завершения вновь получает управление и выполняет капитальную деинициализацию.
 

Фундаментальные основы хакерства

Пятнадцать лет назад эпический труд Криса Касперски «Фундаментальные основы хакерства» был настольной книгой каждого начинающего исследователя в области компьютерной безопасности. Однако время идет, и знания, опубликованные Крисом, теряют актуальность. Редакторы «Хакера» попытались обновить этот объемный труд и перенести его из времен Windows 2000 и Visual Studio 6.0 во времена Windows 10 и Visual Studio 2019.

Ссылки на другие статьи из этого цикла ищи на странице автора.

 

Идентификация стартовых функций

В подавляющем большинстве случаев стартовый код не представляет никакого интереса, и первой задачей при анализе становится поиск функции WinMain. Если компилятор входит в число «знакомых» IDA, она опознает WinMain автоматически, в противном же случае искать функцию приходится руками и головой.

Обычно в штатную поставку компилятора включают исходные тексты его библиотек, в том числе и процедуры стартового кода. Например, у Microsoft Visual C++ стартовый код расположен в файле \VC\crt\src\vcruntime\mcrtexe.cpp. В нем содержится код для инициализации ASCII-версий консольных (main) приложений. В этой же папке лежат еще несколько файлов:

  • mwcrtexe.cpp — код из этого файла используется при старте консольных приложений с Unicode-символами;
  • mcrtexew.cpp — вызывается при запуске Windows-приложений (WinMain) с поддержкой ASCII;
  • mwcrtexew.cpp — служит для запуска Windows-приложений с Юникодом.

После выполнения весьма небольшого блока кода управление из трех последних файлов передается в первый. У Embarcadero C++ Builder 10.3 (в девичестве Borland C++) все файлы со startup-кодом хранятся в отдельной одноименной директории. В частности, файлы, содержащие стартовый код для Windows-приложений, находятся в папке \source\cpprtl\Source\startup\. Ее содержимое несколько похоже на vcruntime тем, что имеется главный файл для запуска Win32-приложений — c0nt.asm. Код из этого файла вызывают другие файлы, содержащие инициализации для подсистем на Win32: DLL, VCL, FMX (приложение FireMonkey, кросс-платформенная графическая подсистема). Если разобраться с исходными текстами, понять дизассемблированный листинг будет намного легче!

Embarcadero C++Builder. Начиная с этой версии у данной системы программирования появилась Community-редакция, которую можно халявно использовать целый год
Embarcadero C++Builder. Начиная с этой версии у данной системы программирования появилась Community-редакция, которую можно халявно использовать целый год

А как быть, если для компиляции исследуемой программы использовался неизвестный или недоступный тебе компилятор? Прежде чем приступать к утомительному ручному анализу, давай вспомним, какой прототип имеет функция WinMain:

int APIENTRY wWinMain(
    _In_ HINSTANCE     hInstance,     // Handle to current instance
    _In_opt_ HINSTANCE hPrevInstance, // Handle to previous instance
    _In_ LPWSTR        lpCmdLine,     // Pointer to command line
    _In_ int           nCmdShow)      // Show state of window

Обрати внимание: APIENTRY замещает WINAPI. Во-первых, четыре аргумента — это достаточно много, и в большинстве случаев WinMain оказывается самой «богатой» на аргументы функцией стартового кода. Во-вторых, последний заносимый в стек аргумент — hInstance — чаще всего вычисляется на лету вызовом функции GetModuleHandleW. То есть, если встретишь конструкцию типа CALL GetModuleHandleW, можно с высокой степенью уверенности утверждать, что следующая функция и есть WinMain. Наконец, вызов WinMain обычно расположен практически в самом конце кода стартовой функции. За ней бывает не более двух-трех «замыкающих» строй функций, например __cexit.

 

Компилили, компилим и будем компилить!

До сего момента мы компилили наши примеры из командной строки:

cl.exe <имя файла>.cpp /EHcs

В результате мы получали строгий, очищенный от мишуры машинный код, после дизассемблирования которого в ассемблерном листинге отсутствовали какие бы то ни было комментарии и внятные названия функций. Теперь мы будем строить наши приложения прямиком из среды разработки (это по-прежнему Visual Studio 2017), чтобы при анализе кода использовать средства, предоставляемые самим компилятором. Мы уже достаточно поупражнялись и базовые конструкции языка высокого уровня можем определять с закрытыми глазами: на ощупь и по запаху!

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

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

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

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

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


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

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

    Подписаться

  • Подписаться
    Уведомить о
    5 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии