Следующая новость
Предыдущая новость

Фундаментальные основы хакерства. Учимся искать ключевые структуры языков высокого уровня

26.02.2020 22:32
Фундаментальные основы хакерства. Учимся искать ключевые структуры языков высокого уровня

Содержание статьи

  • Фундаментальные основы хакерства
  • Идентификация функций
  • Непосредственный вызов функции
  • Вызов функции по указателю
  • Вызов функции по указателю с комплексным вычислением целевого адреса
  • «Ручной» вызов функции инструкцией JMP
  • Автоматическая идентификация функций посредством IDA Pro
  • Пролог
  • Эпилог
  • Итоги

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

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

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

Читай также:

  • Проверка аутентичности и базовый взлом защиты
  • Знакомство с отладчиком
  • Продолжаем осваивать отладчик
  • Новые способы находить защитные механизмы в чужих программах
  • Выбираем лучший редактор для вскрытия исполняемых файлов Windows
  • Мастер-класс по анализу исполняемых файлов в IDA Pro

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

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

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

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

Неплохой сборник, как раз для продолжительной работы

Идентификация функций

Функция (также называемая процедурой или подпрограммой) — основная структурная единица процедурных и объектно ориентированных языков, поэтому дизассемблирование кода обычно начинается с отождествления функций и идентификации передаваемых им аргументов.

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

Откуда функция знает, куда следует возвратить управление? Очевидно, вызывающий код должен предварительно сохранить адрес возврата и вместе с прочими аргументами передать его вызываемой функции. Существует множество способов решения этой проблемы: можно, например, перед вызовом функции поместить в ее конец безусловный переход на адрес возврата, можно сохранить адрес возврата в специальной переменной и после завершения функции выполнить косвенный переход, используя эту переменную как операнд инструкции jump... Не останавливаясь на обсуждении сильных и слабых сторон каждого метода, отметим, что компиляторы в подавляющем большинстве случаев используют специальные машинные команды CALL и RET, соответственно предназначенные для вызова функций и возврата из них.

Инструкция CALL закидывает адрес следующей за ней инструкции на вершину стека, а RET стягивает и передает на него управление. Тот адрес, на который указывает инструкция CALL, и есть адрес начала функции. А замыкает функцию инструкция RET (но внимание: не всякий RET обозначает конец функции!).

Таким образом, распознать функцию можно двояко: по перекрестным ссылкам, ведущим к машинной инструкции CALL, и по ее эпилогу, завершающемуся инструкцией RET. Перекрестные ссылки и эпилог в совокупности позволяют определить адреса начала и конца функции. Немного забегая вперед, заметим, что в начале многих функций присутствует характерная последовательность команд, называемая прологом, которая также пригодна и для идентификации функций. А теперь рассмотрим все эти темы поподробнее.

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

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

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

1 год

5380 р.

1 месяц

720 р.

Я уже участник «Xakep.ru»

Источник

Последние новости