Анализ исполняемых файлов в IDA Pro

Анализ исполняемых файлов IDA Pro

В сегодняшней статье мы окунемся в глубокий и подробный статический анализ с помощью IDA Pro — сверхпопулярного среди хакеров и специалистов по информационной безопасности дизассемблера. С первых же версий IDA Pro занял заслуженное место лидера рынка. Это своего рода швейцарский армейский нож, которым можно раделывать цифровую добычу. Начнем с самого базового анализа и постепенно будем пробираться вперед, разгребая заросли кода.

Анализ исполняемого файла в IDA Pro

С легкой руки Денниса Ритчи повелось начинать освоение нового языка программирования с создания простейшей программы «Hello, World!». Не будем изменять традициям и оценим возможности IDA Pro следующим примером.

Компилятор сгенерирует исполняемый файл объемом почти 190 Кбайт, большую часть которого займет служебный, стартовый или библиотечный код. Попытка дизассемблирования с помощью таких средств, как W32Dasm, не увенчается быстрым успехом, поскольку над полученным листингом размером в два с половиной мегабайта (!) можно просидеть не час и не два. Представьте, сколько времени уйдет на серьезные задачи, требующие изучения десятков и сотен мегабайтов дизассемблированного текста.

Чтобы все было подобно в статье, компилируйте примеры с помощью Visual C++ 2017 вызовом cl.exe first.cpp /EHcs. Флаг /EHcs нужен, чтобы подавить возражение компилятора и вместе с тем включить семантику уничтожения объектов.

Попробуем дизассемблировать даную программу с помощью IDA. Если не изменять настройки по умолчанию, то после завершения анализа файла экран должен выглядеть таким образом:

Результат работы IDA Pro
Результат работы IDA Pro

Чтобы открыть текстовое представление, нужбно открыть контекстное меню и выбрать пункт Text view.

Какую версию IDA Pro выбрать?

На момент написания статьи последней версией IDA Pro была 7.3. Цена программы может показаться великоватой для покупки в исследовательских целях. Как известно, разработчик IDA Pro Ильфак Гильфанов очень строго относится к утечкам и появлению взломанных версий своих продуктов в интернете и жестоко с этим бориться.

Но, несмотря на это на сайте компании Hex-Rays в публичный доступ выложена бесплатная версия дизассемблера с функциональными ограничениями. Например, она не получает обновления после достижения майлстоуна целой версии, то есть сейчас для свободной загрузки доступна версия 7.0. Также она поддерживает только архитектуры x86 и x64.

РЕКОМЕНДУЕМ:
Как убить защитный драйвер в Windows

Тем не менее этого вполне достаточно для наших целей. Потому что у нас нет необходимости разбираться в коде для процессоров ARM, Motorola, Sparc, MIPS или Zilog. Еще одно ограничение накладывается на использование в коммерческих целях, но и в этом случае наша совесть чиста.

Закончив автоматический анализ файла first.exe, IDA переместит курсор к строке .text:0040628B — точке входа в программу. Не забудьте из графического режима отображения листинга переключиться в текстовый. Также обратите внимание на строчку .text:00406290 start endp ; sp-analysis failed, выделенную красным цветом в конце функции start. Так IDA отмечает последнюю строку функции в случае, если у нее в конце return и значение указателя стека на выходе из функции отличается от такового на входе.

Среди новичков в программирование широко распространено заблуждение, что якобы программы, написанные на языке С, начинают выполняться с функции main. В действительности это не совсем так. На самом деле сразу после загрузки файла управление передается на функцию Start, вставленную компилятором. Она подготавливает глобальные переменные:

  • argc — количество аргументов командной строки;
  • argv — массив указателей на строки аргументов;
  • _environ — массив указателей на строки переменных окружения.

Заполняется структура OSVERSIONINFOEX, которая среди прочего включает:

  • dwBuildNumber — билд;
  • dwMajorVersion — старшую версию операционной системы;
  • dwMinorVersion — младшую версию операционной системы;
  • _winver — полную версию операционной системы;
  • wServicePackMajor — старшую версию пакета обновления;
  • wServicePackMinor — младшую версию пакета обновления.

Далее Start инициализирует кучи (heap) и вызывает функцию main, а после возвращения управления завершает процесс с помощью функции Exit. Для получения значений структуры OSVERSIONINFOEX используется функция GetVersionEx.

Продемонстрировать инициализацию переменных, совершаемую стартовым кодом, а также получение их значений позволяет следующая программа.

CRtO.demo.c

Прототип функции main как будто указывает, что приложение не принимает никаких аргументов командной строки, но результат работы программы говорит об обратном и в моем случае выглядит так (приводится в сокращенном виде):

Очевидно, нет никакой необходимости анализировать стандартный стартовый код приложения, и первая задача исследователя — найти место передачи управления на функцию main. К сожалению, чтобы гарантированно решить эту задачу, потребуется полный анализ содержимого функции Start. У специалистов есть много всяких трюков, которые позволяют не делать этого, но все они базируются на особенностях реализации конкретных компиляторов и не могут считаться универсальными.

Например, Visual C++ всегда, независимо от прототипа функции main, передает ей три аргумента: указатель на массив указателей переменных окружения, указатель на массив указателей аргументов командной строки и количество аргументов командной строки, а все остальные функции стартового кода принимают меньшее количество аргументов.

Советую ознакомиться с исходниками стартовых функций популярных компиляторов. Для Visual C++ 14 в соответствии с архитектурой они находятся в подпапках каталога %\Program Files (x86)\Microsoft Visual Studio 14.0\VC\crt\src\. Их изучение упростит анализ дизассемблерного листинга.

Ниже в качестве иллюстрации приводится фрагмент стартового кода программы first.exe, полученный в результате работы W32Dasm.

Иначе выглядит результат работы IDA, умеющей распознавать библиотечные функции по их сигнатурам (приблизительно по такому же алгоритму работает множество антивирусов).

Поэтому способности дизассемблера тесно связаны с его версией и полнотой комплекта поставки — далеко не все версии IDA Pro в состоянии работать с программами, сгенерированными современными компиляторами.

Перечень поддерживаемых компиляторов вы можете найти в файле %IDA%/SIG/list. В нем есть старинные Microsoft C и Quick C, Visual C++ с первой по восьмую версию и Visual.Net. А вот Visual C++ 14 из Visual Studio 2017 здесь нет. Однако, взглянув в окно IDA, мы видим, что дизассемблер сумел определить многие (но не все) функции.

Заглянем в окно вывода, находящееся внизу. Там, немного прокрутив вывод, мы обнаружим строчку Using FLIRT signature: SEH for vc7-14, говорящую о том, что используемая версия IDA все же понимает компиляторы Visual C++ от 7 до 14.

Текстовое отображение результата работы IDA
Текстовое отображение результата работы IDA

Попрубем разобраться в получившемся листинге. Первое и в данном случае единственное, что нам надо найти, — это функция main. В начале стартового кода после выполнения процедуры sub_406992 программа совершает прыжок на метку loc_406109:

Для перехода на метку достаточно поставить на нее текстовый курсор и нажать Enter. Если подвести курсор мыши к метке или вызову функции, появится окно, в котором будет показано начало выбранной функции или место листинга, куда переводит метка, что очень удобно.

В данном случае, как мы видим по комментарию, IDA отправила нас в начало стартового куска кода. Немного прокрутим листинг вниз, обращая внимание на плавные переходы по меткам.

РЕКОМЕНДУЕМ:
Способы обхода защиты и взлома программ

В итоге доходим до вызова функции: call    sub_4010D0. Похоже, это и есть функция main, поскольку здесь дизассемблер смог распознать строковую переменную и дал ей осмысленное имя aHelloSailor, а в комментарии, расположенном справа, для наглядности привел оригинальное содержимое Hello, Sailor!\n. Смещение этой строки компилятор закинул на вершину стека, а затем ниже через строчку, по всей видимости, происходит вызов функции вывода на экран:

Чтобы убедиться в этом, зайдем в последнюю процедуру. Разобраться в ней без 100 грам не представляется возможным, разве что огромное количество параметров намекает нам на схожесть с функцией printf.

Если поместить курсор в границы имени aHelloSailor и нажать Enter, IDA автоматически перейдет к строке, в которой выполняется определение переменной:

Выражение DATA XREF: sub_4010D0+3o называется перекрестной ссылкой и свидетельствует о том, что в третьей строке процедуры sub_4010D0 произошло обращение к текущему адресу по его смещению ( o от слова offset), а стрелка, направленная вверх, указывает на относительное расположение источника перекрестной ссылки.

Если навести курсор на выражение sub_4010D0+3o и нажать Enter, то IDA Pro перейдет к следующей строке:

Нажатие Esc отменяет предыдущее перемещение, возвращая курсор в исходную позицию.

К слову, дизассемблер W32Dasm даже не смог распознать строковую переменную.

Что не так с IDA?

Положа руку на сердце — я был слегка разочарован, ибо ожидал, что IDA распознает больше библиотечных процедур. Поэтому я решил натравить «Иду» на такую же программу, но сгенерированную более ранней версией компилятора. Подопытным кроликом был Visual C++ 8.0 (VS 2005).

Сравним результаты работы компиляторов. Тот же исходник, компиляция из командной строки (папка first05). Загрузим итоговый экзешник в «Иду». Листинг приводится в сокращенном виде для экономии пространства.

Текстовое отображение результата работы IDA
Текстовое отображение результата работы IDA

Мало того что стартовый код меньше по объему, так еще было автоматически определено большее количество библиотечных функций, среди которых: GetVersionExA, GetProcessHeap, HeapFree и ряд других. Среди них достаточно просто найти вызов main и перейти на саму функцию.

Тем не менее VC++ 8.0 — ушедшая эпоха, и в пример я ее привел только для ознакомления.
Тем не менее VC++ 8.0 — ушедшая эпоха, и в пример я ее привел только для ознакомления.

На этом анализ приложения first.cpp можно считать завершенным. Для полноты картины остается переименовать функцию sub_4010D0 в main. Для этого подведите курсор к строке .text:004010D0 (началу функции) и жмите N. В появившемся диалоге можете ввести main. Результат должен выглядеть так:

Обратите внимание: IDA в комментарии подставила прототип функции, а ниже параметры по умолчанию.

Анализ зашифрованных программ в IDA

Другое важное преимущество IDA — возможность дизассемблировать зашифрованные программы. В примере Crypt00.com используется статическое шифрование, которое часто встречается в «конвертных» защитах. Между тем этот файл не запустится в Windows 10, поскольку *.com для работы требует 16-разрядную исполняемую среду.

Я думаю, это не повод отказаться от анализа столь интересного примера, тем более что существуют мощные средства виртуализации, поэтому поставить 32-битную Windows XP, которая выполняет 16-битные проги, — не проблема. К тому же анализ файлов .com значительно проще, чем .exe, так как первые сильно короче вторых.

Мы уже видели непроходимые заросли библиотечного кода, вставленного компилятором в минимальной exe-программе, тогда как в com все по минимуму, в чем мы скоро убедимся. Отмечу также, что последней версией IDA Pro, работающей в 32-разрядных средах, была 6.8.

Sourcer в деле
Sourcer в деле

Рассматриваемый прием шифрования полностью «ослепляет» большинство дизассемблеров. Например, результат обработки файла Crypt00.com при помощи Sourcer выглядит так:

Самостоятельно Sourcer не сумел дизассемблировать половину кода, оставив ее в виде дампа. С другой стороны, как-то помочь ему мы не можем. Напротив, IDA изначально проектировалась как дружественная к пользователю интерактивная среда. В отличие от Sourcer-подобных дизассемблеров, IDA не делает никаких молчаливых предположений и при возникновении проблем обращается за помощью к человеку. Результат анализа «Идой» файла Crypt00.com выглядит так:

И тут необходимо помочь дизассемблеру. Новички в этой ситуации обычно ставят курсор на соответствующую строку и нажимают клавишу C, заставляя IDA дизассемблировать код с текущей позиции до конца функции. Несмотря на кажущуюся очевидность, такое решение ошибочно.

Данные

Что представляет собой число 10Dh в строке 0x100 — константу или смещение? Очевидно, в регистр SI заносится смещение, потому что впоследствии операнд по этому смещению в памяти интерпретируется как байт и над ним выполняется операция XOR.

Чтобы преобразовать константу в смещение, установите текстовый курсор на 10Dh и нажмите O. Дизассемблируемый текст станет выглядеть так:

IDA Pro автоматически создала новое имя locret_1010D, которое ссылается на зашифрованный блок кода. Попробуем преобразовать его в данные. Для этого надо поставить курсор на строку 010D и дважды нажать D, чтобы утвердительно ответить на вопрос во всплывающем диалоге. Листинг примет следующий вид:

Но на что именно указывает word_1010D? Понять это позволит изучение следующего кода:

После того как в регистр SI попадает смещение, начинается цикл, который представляет собой простейший расшифровщик: значение в регистре SI указывает на символ, команда XOR с помощью числа 0x77 расшифровывает один байт (один ASCII-символ). Напомню, в ассемблере запись шестнадцатеричных чисел вида 77h. После этого инкрементируется значение регистра SI (указатель переводится на следующий символ) и получившееся значение сравнивается с числом 0x124, которое равно общему количеству символов для расшифровки.

РЕКОМЕНДУЕМ:
Отладка MIPS с помощью GDB

Здесь следует остановиться и, прокрутив дизассемблированный листинг вниз, обратить внимание на то, какое смещение максимальное. Видим: это 0x123. Область зашифрованных данных может продолжаться до конца программы, тем не менее это значение меньше, чем количество байтов для расшифровки.

Давайте расширим сегмент подопытной программы. Жмем Shift-F7 (View → Open subviews → Segments), откроется вкладка Program Segmentation. В контекстном меню единственного существующего сегмента seg000 выбираем пункт Edit Segments (Ctrl-E). В диалоге в поле ввода End address введите значение побольше, например 0x10125. Подтвердите свое намерение в появившемся диалоге.

Изменение атрибутов сегмента
Изменение атрибутов сегмента

Можете полюбоваться на увеличившийся сегмент. Вернемся к изучению кода. Если в результате сравнения значение в регистре SI меньше общего количества байтов или равно ему, выполняется переход на метку loc_10103 и блок кода повторяется для расшифровки следующего байта. Отсюда можно заключить, что word_1010D указывает на начало последовательности байтов для расшифровки. Подведя к ней курсор, жмем N и можем дать ей осмысленное имя, например BeginCrypt. А константу 124h можем сначала преобразовать в смещение (Ctrl-O), а затем переименовать, например в EndCrypt.

Расшифровка

Непосредственное дизассемблирование зашифрованного кода невозможно — предварительно его необходимо расшифровать. Большинство дизассемблеров не умеют модифицировать анализируемый текст на лету, и до загрузки в дизассемблер исследуемый файл должен быть полностью расшифрован.

На практике, однако, это выглядит несколько иначе. Прежде чем расшифровывать, необходимо выяснить алгоритм расшифровки, проанализировав доступную часть файла. Затем нужно выйти из дизассемблера, тем или иным способом расшифровать «секретный» фрагмент, вновь загрузить файл в дизассемблер (причем предыдущие результаты работы дизассемблера окажутся утерянными) и продолжить его анализ до тех пор, пока не встретится еще один зашифрованный фрагмент. Теперь цикл «выход из дизассемблера — расшифровка — загрузка — анализ» повторяется вновь.

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

Слева каждой строки указывается имя сегмента и его смещение, например seg000:0103. Но нам нужно другое значение. Установив текстовый курсор на нужную строку, смотрите на нижнюю часть текущей вкладки (полагаю, у вас это IDA View-A).

Фактический номер строки
Фактический номер строки

При перемещении курсора соответствующее смещение тоже меняется (на рисунке выше оно обведено рамочкой). С его помощью можно обратиться к любой ячейке сегмента. Для чтения и модификации ячеек предусмотрены функции Byte и PatchByte соответственно. Их вызов может выглядеть, например, так: a=Byte(0x01010D) читает ячейку, расположенную по смещению 0x01010D; PatchByte(0x01010D,0x27) присваивает значение 0x27 ячейке памяти, расположенной по смещению 0x01010D. Как следует из названия функций, они манипулируют ячейками размером в один байт.

Знания языка C и этих двух функций вполне достаточно для написания скрипта-расшифровщика.

Реализация IDA-С не полностью придерживается стандарта, в частности IDA не позволяет разработчику задавать тип переменной и определяет его автоматически по ее первому использованию, а объявление осуществляется ключевым словом auto. Например, auto MyVar, s0 объявляет две переменные — MyVar и s0.

Для создания скрипта необходимо нажать Shift-F2 или выбрать в меню File пункт Script Command. В результате откроется окно Execute script. Большую его часть занимают список скриптов и поле ввода для редактирования выбранного скрипта.

Дополнительно внизу окна находятся ниспадающий список для выбора используемого языка (IDC или Python), ниспадающий список для указания размера табуляции и четыре кнопки: Run (выполнить скрипт), Export (экспортировать скрипт в файл), Import (загрузить скрипт из файла) и Save (сохранить скрипт в базу данных проекта).

После первого открытия окна в списке скриптов по умолчанию выбран скрипт Default snippet. В качестве его тела введем такой код:

Встроенный редактор скриптов
Встроенный редактор скриптов

Как было показано выше, алгоритм расшифровщика сводится к последовательному преобразованию каждой ячейки зашифрованного фрагмента операцией XOR 0x77:

Сам же зашифрованный фрагмент начинается с адреса 0x01010D и продолжается вплоть до 0x010123.

В конце командой Message отправляем модифицированный символ в область вывода IDA.

Чтобы запустить скрипт на выполнение, достаточно нажать кнопку Run. Если при вводе скрипта не было опечаток, листинг примет следующий вид:

А в окне вывода появится надпись

Возможные ошибки: несоблюдение регистра символов (IDA к этому чувствительна), синтаксические ошибки, неверно заданные адреса границ модифицируемого блока. В случае ошибки необходимо подвести курсор к строке seg000:010D, нажать клавишу U (для удаления результатов предыдущего дизассемблирования зашифрованного фрагмента) и затем C (для повторного дизассемблирования расшифрованного кода).

Символы перед фразой «Hello, World!» не выглядят читаемыми; скорее всего, это не ASCII, а исполняемый код. Поставим курсор на строку seg000:010D, жмем C («Преобразовать в инструкцию»). В результате листинг будет выглядеть так:

Цепочку символов, расположенную начиная с адреса seg000:0115, можно преобразовать в удобочитаемый вид, если навести на нее курсор и нажать A. Еще можно преобразовать константу 115h в строке 010F в смещение. Теперь экран дизассемблера будет выглядеть так:

Команда MOV AH, 9 в строке seg000:010D подготавливает регистр AH перед вызовом прерывания 0x21. Она выбирает функцию вывода строки на экран, а ее смещение следующей командой заносится в регистр DX. Иными словами, для успешного ассемблирования листинга необходимо заменить константу 0x115 соответствующим смещением.

Но ведь выводимая строка на этапе ассемблирования (до перемещения кода) расположена совсем в другом месте! Одно из возможных решений этой проблемы — создать новый сегмент и затем скопировать в него расшифрованный код. Это будет аналогом перемещения кода работающей программы.

Создание сегмента

Для создания нового сегмента надо открыть вкладку Segments (Shift-F7) и нажать Insert. Появится вот такое окно.

Использование IDA Pro Создание нового сегмента
Создание нового сегмента

Базовый адрес сегмента может быть любым, если при этом не перекрываются сегменты seg000 и MySeg; начальный адрес сегмента задается так, чтобы смещение первого байта было равно 0x100; размер нового сегмента сделаем таким же, как seg000. Не забудьте выбрать тип создаваемого сегмента: 16-битный сегмент.

Далее будем двигаться поэтапно. Сначала скопируем команды для вывода символов в консоль. Начнем брать байты со смещения 10D сегмента seg000, а вставлять — с самого начала сегмента MySeg. Это можно сделать скриптом следующего содержания:

Для его ввода необходимо вновь нажать комбинацию клавиш Shift-F2. Создать еще один скрипт можно нажатием Insert. После выполнения экран дизассемблера будет выглядеть так (показано только начало сегмента MySeg):

Надо преобразовать данные в инструкции: поставить курсор на строку MySeg:0100 и нажать C. Листинг примет ожидаемый вид:

Чтобы программа-клон вела себя немного не так, как ее родитель, добавим ожидание ввода символа. Для этого надо поставить курсор на команду retn и выбрать Edit → Patch program → Assemble…

Введите XOR AX, AX, нажмите Enter. Затем INT 16h, снова Enter. Последняя инструкция — RET, Enter и Esc для закрытия диалога.

Использование IDA Pro Замена инструкции
Замена инструкции

Теперь с помощью следующего скрипта скопируем байты, составляющие текст «Hello, World!»:

Поставив курсор на строку MySeg:010C, нажимаем A и преобразуем цепочку символов в удобочитаемый вид. В строке MySeg:0102 надо изменить константу 115h на фактическое значение, по которому расположена фраза для вывода: MySeg:010C. Для этого ставьте курсор на указанную строку и открывайте диалог Assemble Instruction (Edit → Patch program → Assemble…) и введите MOV DX, 10Ch.

РЕКОМЕНДУЕМ:
Режим гаммирования в блочном алгоритме шифрования

Теперь надо преобразовать константу 10Ch в смещение, а последовательность символов, расположенную по нему, обратить к светскому виду. Как это делать, вы уже знаете. Напоследок рекомендую произвести косметическую чистку — уменьшить размер сегмента до необходимого. Чтобы удалить адреса, оставшиеся при уменьшении размеров сегмента за его концом, поставьте флажок Disable Address в окне свойств сегмента.

Казалось бы, можно уже начать ассемблирование, однако не стоит спешить! У нас в программе есть три смещения, которые являются указателями. В дизассемблированном листинге они выглядят прекрасно:

  1. mov     si, offset BeginCrypt в сегменте seg000;
  2. cmp     si, offset EndCrypt в сегменте seg000;
  3. mov     dx, offset aHelloWord_0 в сегменте MySeg.

Между тем после ассемблирования символьные имена будут заменены числовыми константами. Будут ли в таком случае в результирующей программе эти смещения указывать на те же вещи, что в исходной? Давайте еще раз проанализируем наш листинг. Первое смещение, BeginCrypt, указывает на строку seg000:010D. По сути, весь предыдущий код будет скопирован, поэтому ее значение менять не нужно. Второе смещение, EndCrypt, указывающее на конец сегмента, должно увеличиться на четыре байта, так как мы добавили две инструкции:

Чтобы подсчитать их размер, достаточно из следующего за ними смещения вычесть их начальное: 118h – 114h = 4h байта. В результате EndCrypt должно указывать на 124h + 4h = 128h. Установите курсор на строку seg000:0107, вызовите окно ассемблера и замените инструкцию в ней на cmp si, 128h.

Третье смещение, aHelloWord_0, в исходной программе равно 10Ch. Подумаем, как должен измениться адрес. Если aHelloWord_0 находится в сегменте MySeg, то нам просто нужно прибавить к имеющемуся смещению размер расшифровщика в сегменте seg000. Его можно посчитать как разность начального смещения зашифрованного блока и начального адреса: 0x010D – 0x0100 = 0xD байт.

В итоге смещение aHelloWord_0 должно указывать на 0x10C + 0xD = 0x119. Изменим код: установив курсор на строку MySeg:0102, с помощью встроенного ассемблера модифицируем ее содержимое на mov dx, 119h.

В результате полный листинг выглядит следующим образом:

Создание клона

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

Для решения этой задачи напишем скрипт, автоматически выполняющий копирование и шифрование необходимых частей программы:

Подобный механизм копирования своего тела во внешний файл используется подавляющим большинством вирусов.

Итоги

Выполнение скрипта приведет к созданию файла crypt01.com, запустив который можно убедиться в его работоспособности — он выводит строку на экран и, дождавшись нажатия любой клавиши, завершает работу.

Использование IDA Клонированное приложение
Клонированное приложение

Огромным преимуществом такого подхода является сквозная компиляция файла, то есть дизассемблированный листинг в действительности не ассемблировался! Вместо этого из виртуальной памяти байт за байтом читалось оригинальное содержимое, которое за исключением модифицированных строк идентично исходному файлу. Напротив, повторное ассемблирование практически никогда не позволяет добиться полного сходства с дизассемблируемым файлом.

Дизассемблер и отладчик в связке

Дизассемблер, включенный в отладчик, обычно слишком примитивен и не может похвастаться богатыми возможностями. Во всяком случае, дизассемблер, встроенный в WinDbg, недалеко ушел от DUMPBIN, с недостатками которого мы уже сталкивались. Насколько же понятнее становится код, если его загрузить в IDA!

Чем же тогда ценен отладчик? Дело в том, что дизассемблер из-за своей статичности имеет ряд ограничений. Во-первых, исследователю приходится выполнять программу на «эмуляторе» процессора, «зашитом» в его собственной голове, следовательно, необходимо знать и назначение всех команд процессора, и всех структур операционной системы (включая недокументированные).

Во-вторых, начать анализ с произвольного места программы не так-то просто — требуется знать содержимое регистров и ячеек памяти на текущий момент, а как их узнать? С регистрами и локальными переменными еще не так плохо — достаточно покрутить экран дизассемблера вверх и посмотреть, какие значения им присваиваются. Но этот фокус не пройдет с глобальными переменными, модифицировать которые может кто угодно и когда угодно. Вот бы установить точку останова… но какая же в дизассемблере может быть точка останова?

РЕКОМЕНДУЕМ:
Распаковка исполняемых файлов на примере банковского трояна GootKit

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

Можно перечислять бесконечно, но и без того ясно, что отладчик отнюдь не конкурент дизассемблеру, а, напротив, партнер. Опытные хакеры всегда используют эти инструменты в паре. Алгоритм реконструируется в дизассемблере, а все непонятные моменты оперативно уточняются прогоном программы под отладчиком.

При этом возникает естественное желание видеть в отладчике все те символьные имена, которые были внесены в дизассемблерный листинг. И IDA Pro предоставляет два способа это сделать! Рассмотрим оба.

Способ 1

Вернемся в Windows 10 и загрузим в IDA Pro файл first.exe (или ранее созданный «Идой» проект). Выберем в меню File подменю Produce file, а в нем — пункт Create MAP file. На экране появится окно с запросом имени файла (введем, например, first.map), а затем откроется модальный диалог, уточняющий, какие имена стоит включать в map-файл. Нажмем Enter, чтобы оставить все галочки в состоянии по умолчанию.

Мгновение спустя на диске образуется файл first.map, содержащий всю необходимую отладочную информацию в map-формате Borland. Отладчик WinDbg не поддерживает такой формат, поэтому перед его использованием файл необходимо конвертировать в формат DBG — отладочный формат Microsoft.

Конвертировать можно с помощью утилиты map2dbg, свободно распространяемой вместе с исходными кодами. Запускать ее нужно из командной строки. В один каталог с ней кладем map-файл и соответствующий .exe. Затем в нашем случае выполняем команду map2dbg first.exe.

В результате утилита выведет число преобразованных символов, а в текущей папке будет создан новый файл с расширением dbg. Теперь можно загрузить first.exe в WinDbg. При этом, если first.dbg находится в том же каталоге, файл подхватится автоматически и будет скопирован в системную папку C:\ProgramData\dbg\sym\first.dbg\ для дальнейшего исследования экзешника.

Сейчас в WinDbg надо выполнить команду .reload /f. Она заставит отладчик перезагрузить информацию из модулей. Затем можете выполнить lm, чтобы увидеть список загруженных модулей. Модуль first будет отмечен как codeview symbols, иначе было бы deferred:

Исполнение команды x first!* выведет все символы в файле first.exe (показана только малая часть списка):

Посмотрим содержимое системной переменной:

Способ 2

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

У бесплатной версии IDA Pro есть и другие недостатки: некорректное создание выходных файлов (в том числе map-файла), некорректное построение диаграммы и прочие пока не затронутые нами вещи.

Теперь можно подключить WinDbg к IDA. Для этого надо открыть конфигурационный файл ida.cfg, находящийся в каталоге С:\Program Files\IDA 7.0\cfg\. Проматываем его содержимое до такой строки:

Ниже или вместо нее вставить путь к инструментам отладки WinDbg. В моем случае:

Отсюда вытекает третья мудрость: в случае с IDA 7.0 и новее, даже если приложение дизассемблируется и отлаживается в 32-битной разновидности IDA, путь надо указывать к 64-битной WinDbg. Иначе при запуске отладки вас ждет сообщение об ошибке.

Следующим шагом надо запустить IDA, выбрать WinDbg из ниспадающего списка на панели задач и, нажав F9, запустить отладку. Только отладка приложения first.exe закончится, не успев начаться. Нужно сделать так, чтобы на точке входа программа замерла. Для этого надо вызвать диалоговое окно Debugger setup (пункт Debugger options в меню Debugger).

IDA Pro как пользоваться Параметры отладчика
Параметры отладчика

Нашу задачу призвана решить область Events. Здесь можно выбрать, на каких событиях мы хотим подвешивать программу. Поставим третий флажок Suspend on process entry point, в результате чего выполнение проги приостановится на стартовом коде.

IDA Pro как пользоваться. Выполнение программы было приостановлено на точке входа
Выполнение программы было приостановлено на точке входа

Знакомые места? Еще бы! Обратите внимание: программа оперирует регистрами процессорной архитектуры x86-64: RCX, RDX, RCI и так далее. Ядро Windows 10 экспортирует 1595 символов, учитывая все установленные обновления операционной системы на моем компьютере.

Это можно проверить, дважды щелкнув на модуле kernel32.dll в окне Modules во время отладки IDA Pro. Откроется дополнительная вкладка Module: KERNEL32.DLL. Ее можно отцепить и перетащить в любое место. На нижней части рамки окна отображается общее количество символов, экспортируемых данным модулем.

Подключение WinDbg к IDA позволяет «Иде» использовать символьную информацию модулей с публичного сервера Microsoft. Для этого можно создать переменную окружения или определить директорию непосредственно из IDA без ее перезапуска. Пойдем второй, более короткой дорогой, а создать переменную окружения вы можете на досуге. В командную строку IDA (в нижней части окна рядом с кнопкой WINDBG) введи:

После этого перезагрузите символы командой .reload /f. Число экспортируемых модулем kernel32.dll символов стало 5568.

Теперь символьные имена не только отображаются на экране, что упрощает понимание кода, — на любое из них можно быстро и с комфортом установить точку останова (скажем, bp GetProcAddress), и отладчик поймет, что от него хотят! Нет больше нужды держать в памяти эти трудно запоминаемые шестнадцатеричные адреса!

РЕКОМЕНДУЕМ:
Взлом приложений для Андроид с помощью отладчика

Заключение

IDA — очень удобный инструмент для модификации файлов, исходные тексты которых утеряны или отсутствуют. Это практически единственный дизассемблер, способный анализировать зашифрованные программы, не прибегая к сторонним средствам. Ну и конечно, IDA обладает отличным пользовательским интерфейсом и удобной системой навигации по исследуемому тексту. В результате с таким инструментом в руках вы можете справиться с любой мыслимой и немыслимой задачей… если, конечно, владеете языком скриптов, что и подтвердили приведенные выше примеры.

Понравилась статья? Поделиться с друзьями:
Добавить комментарий