Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Исследование алгоритма работы программ, написанных на языках высокого уровня, традиционно начинается с реконструкции ключевых структур исходного языка — функций, локальных и глобальных переменных, ветвлений, циклов и так далее. Это делает дизассемблированный листинг более наглядным и значительно упрощает его анализ.
Пятнадцать лет назад эпический труд Криса Касперски «Фундаментальные основы хакерства» был настольной книгой каждого начинающего исследователя в области компьютерной безопасности. Однако время идет, и знания, опубликованные Крисом, теряют актуальность. Редакторы «Хакера» попытались обновить этот объемный труд и перенести его из времен Windows 2000 и Visual Studio 6.0 во времена Windows 10 и Visual Studio 2017.
Читай также:
Современные дизассемблеры достаточно интеллектуальны и львиную долю распознавания ключевых структур берут на себя. В частности, IDA Pro успешно справляется с идентификацией стандартных библиотечных функций, локальных переменных, адресуемых через регистр ESP
, case-ветвлений и прочего. Однако порой она ошибается, вводя исследователя в заблуждение, к тому же ее высокая стоимость не всегда оправдывает применение. Например, студентам, изучающим ассемблер (а лучшее средство изучения ассемблера — дизассемблирование чужих программ), она едва ли по карману.
Разумеется, на IDA свет клином не сошелся, существуют и другие дизассемблеры — скажем, тот же DUMPBIN, входящий в штатную поставку SDK. Почему бы на худой конец не воспользоваться им? Конечно, если под рукой нет ничего лучшего, сойдет и DUMPBIN, но в этом случае об интеллектуальности дизассемблера придется забыть и пользоваться исключительно своей головой.
Первым делом мы познакомимся с неоптимизирующими компиляторами — анализ их кода относительно прост и вполне доступен для понимания даже новичкам в программировании. Затем же, освоившись с дизассемблером, перейдем к вещам более сложным — оптимизирующим компиляторам, генерирующим очень хитрый, запутанный и витиеватый код.
Поставь любимую музыку, выбери любимый напиток и погрузись в глубины дизассемблерных листингов.
Неплохой сборник, как раз для продолжительной работы
Функция (также называемая процедурой или подпрограммой) — основная структурная единица процедурных и объектно ориентированных языков, поэтому дизассемблирование кода обычно начинается с отождествления функций и идентификации передаваемых им аргументов.
Строго говоря, термин «функция» присутствует не во всех языках, но даже там, где он присутствует, его определение варьируется от языка к языку. Не вдаваясь в детали, мы будем понимать под функцией обособленную последовательность команд, вызываемую из различных частей программы. Функция может принимать один и более аргументов, а может не принимать ни одного; может возвращать результат своей работы, а может и не возвращать — это уже не суть важно. Ключевое свойство функции — возвращение управления на место ее вызова, а ее характерный признак — множественный вызов из различных частей программы (хотя некоторые функции вызываются лишь из одного места).
Откуда функция знает, куда следует возвратить управление? Очевидно, вызывающий код должен предварительно сохранить адрес возврата и вместе с прочими аргументами передать его вызываемой функции. Существует множество способов решения этой проблемы: можно, например, перед вызовом функции поместить в ее конец безусловный переход на адрес возврата, можно сохранить адрес возврата в специальной переменной и после завершения функции выполнить косвенный переход, используя эту переменную как операнд инструкции jump
… Не останавливаясь на обсуждении сильных и слабых сторон каждого метода, отметим, что компиляторы в подавляющем большинстве случаев используют специальные машинные команды CALL
и RET
, соответственно предназначенные для вызова функций и возврата из них.
Инструкция CALL
закидывает адрес следующей за ней инструкции на вершину стека, а RET
стягивает и передает на него управление. Тот адрес, на который указывает инструкция CALL
, и есть адрес начала функции. А замыкает функцию инструкция RET
(но внимание: не всякий RET
обозначает конец функции!).
Таким образом, распознать функцию можно двояко: по перекрестным ссылкам, ведущим к машинной инструкции CALL
, и по ее эпилогу, завершающемуся инструкцией RET
. Перекрестные ссылки и эпилог в совокупности позволяют определить адреса начала и конца функции. Немного забегая вперед, заметим, что в начале многих функций присутствует характерная последовательность команд, называемая прологом, которая также пригодна и для идентификации функций. А теперь рассмотрим все эти темы поподробнее.
Источник: xakep.ru