Если первого встречного программиста спросить, с какой функции начинается выполнение 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»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


Крис Касперски

Крис Касперски

Известный российский хакер. Легенда ][, ex-редактор ВЗЛОМа. Также известен под псевдонимами мыщъх, nezumi (яп. 鼠, мышь), n2k, elraton, souriz, tikus, muss, farah, jardon, KPNC.

Юрий Язев

Юрий Язев

Широко известен под псевдонимом yurembo. Программист, разработчик видеоигр, независимый исследователь. Старый автор журнала «Хакер».

Check Also

NPM Hijacking. Встраиваем произвольный код в приложения на Node.js

С веб‑стра­ниц JavaScript при­шел на сер­веры, а с сер­веров — на дес­кто­пы, и ста­рые ба…

5 комментариев

  1. Аватар

    slinkin

    12.04.2020 в 23:02

    Спасибо! Отличная статья. В начале подача получилась несколько сумбурная, но затем все стало излагаться последовательно.

    Касательно виртуальных функций — существует Юли подход, что бы дать имена таким функциям во время реверса. Допасти есть вызов

    call qword ptr [rdx+8]

    Как дать понять IDA-е что по этому адресу demo2? Чтобы в последствии она всегда отображала как call qword demo2

    • Юрий Язев

      Юрий Язев

      29.04.2020 в 01:14

      Четкий вопрос! Подводишь курсор мыши к строчке «call qword ptr [rdx+8]», вызываешь контекстное меню, в нем выбираешь пункт «Manual…», в появившемся окне в строку ввода вбиваешь удобное название, которое хочешь присвоить, например, как в нашем случае: «Base::demo_2».

  2. Аватар

    s00mbre

    18.04.2020 в 16:13

    Хорошая статья, спасибо! Но… ай-яй-яй, в тестовом коде утечка памяти))
    delete p;

Оставить мнение