Jump to content
Search In
  • More options...
Find results that contain...
Find results in...
Sign in to follow this  
mr.save

Фундаментальные основы хакерства. Мастер-класс по анализу исполняемых файлов в IDA Pro

Recommended Posts

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

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

Скрытый контент

    Дайте реакцию на этот пост, чтобы увидеть скрытый контент.

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

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

Скрытый контент

    Дайте реакцию на этот пост, чтобы увидеть скрытый контент.

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

Цитата

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

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

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

Тем не менее этого вполне достаточно для наших целей. Потому что нам не придется разбираться в коде для процессоров 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.

Скрытый контент

    Дайте реакцию на этот пост, чтобы увидеть скрытый контент.

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

Скрытый контент

    Дайте реакцию на этот пост, чтобы увидеть скрытый контент.

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

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

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

Скрытый контент

    Дайте реакцию на этот пост, чтобы увидеть скрытый контент.

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

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

Скрытый контент

    Дайте реакцию на этот пост, чтобы увидеть скрытый контент.

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

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

Скрытый контент

    Дайте реакцию на этот пост, чтобы увидеть скрытый контент.

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

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

Цитата

 

Что не так с IDA?

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

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

Скрытый контент

    Дайте реакцию на этот пост, чтобы увидеть скрытый контент.

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

Тем не менее 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.

Скрытый контент

    Дайте реакцию на этот пост, чтобы увидеть скрытый контент.

Рассматриваемый прием шифрования полностью «ослепляет» большинство дизассемблеров. Например, результат обработки файла 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, которое равно общему количеству символов для расшифровки.

Здесь следует остановиться и, прокрутив дизассемблированный листинг вниз, обратить внимание на то, какое смещение максимальное. Видим: это 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. Появится вот такое окно.

Скрытый контент

    Дайте реакцию на этот пост, чтобы увидеть скрытый контент.

Базовый адрес сегмента может быть любым, если при этом не перекрываются сегменты 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 для закрытия диалога.

Скрытый контент

    Дайте реакцию на этот пост, чтобы увидеть скрытый контент.

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

Скрытый контент

    Дайте реакцию на этот пост, чтобы увидеть скрытый контент.

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

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

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

  • mov si, offset BeginCrypt в сегменте seg000;
  • cmp si, offset EndCrypt в сегменте seg000;
  • 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, запустив который можно убедиться в его работоспособности — он выводит строку на экран и, дождавшись нажатия любой клавиши, завершает работу.

Скрытый контент

    Дайте реакцию на этот пост, чтобы увидеть скрытый контент.

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

 

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

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

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

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

В-третьих, дизассемблирование вынуждает реконструировать алгоритм каждой функции, в то время как отладка позволяет рассматривать ее как «черный ящик» со входом и выходом. Допустим, имеется у нас функция, которая расшифровывает основной модуль программы. В дизассемблере нам придется сначала разобраться в алгоритме шифрования (что может оказаться совсем не просто), затем переложить эту функцию на язык 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 не позволяет отлаживать программы. Второе замечание не менее важное: в пути к отлаживаемой программе не должно быть символов кириллицы.

Теперь можно подключить 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).

Скрытый контент

    Дайте реакцию на этот пост, чтобы увидеть скрытый контент.

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

Скрытый контент

    Дайте реакцию на этот пост, чтобы увидеть скрытый контент.

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

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

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

Скрытый контент

    Дайте реакцию на этот пост, чтобы увидеть скрытый контент.

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

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

 

Заключение

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

Share this post


Link to post

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Sign in to follow this  

×
×
  • Create New...