|
|||||||||||||||||||||||||||||||||||||||||||
Автор: GPcH. Дата публикации: 29.08.2005
|
Дизассемблер своими рукамиКак часто ты слышал про дизассемблеры? Думаю не стоит объяснять что это такое и для чего это нужно. Ты наверняка не раз отлаживал свою (или чужую :) программу под IDA или в Olly Debugger’е а также несомненно знаком с hiew’ом. Все эти проги довольно шустро раскладывают скомпилированный EXE файл на ассемблерный код который ты можешь изучать и изменять. В этой статье я опишу как можно уже сейчас замутить простенький дизассемблер под свои нужды.Введение Для начала думаю нелишне напомнить тебе, для чего все же тебе может пригодиться самодельный дизассемблер. Знаком ли ты с EXE упаковщиками? Это такие проги которые способны пожать твою прогу в 2 или более раза, при этом оставив ее работоспособной. Из бесплатных представителей таких прог ты наверняка помнишь опенсорсный UPX. А задумывался ли ты как пишут распаковщики для таких прог? Вряд ли. А зря! Ядро распаковщиков, особенно статических (которые распаковывают программу без запуска) основано именно на дизассемблере, который в совокупности с анализатором кода позволяет понять код распаковываемой программы и распаковать ее используя нужный алгоритм упаковщика. Если ты когда-нибудь решишь написать такой распаковщик - тебе без самопального дизассемблера не обойтись. А задумывался ли ты как работают все современные защиты программ? Чтобы извратить код программы до неузнаваемости, дабы его не смог понять взломщик, они дизассемблируют твою прогу команда за командой, делая код метаморфным, попутно внедряя мусорные инструкции. Я уже не говорю про виртуальные машины, которые вообще представляют собой кодер-декодер ассемблерных команд в псевдокод и наоборот. Надеюсь я убедил тебя в том, что без дизассемблера никуда, особенно если тебя увлекает все, что связано с PE форматом программ. Дизассемблирование Ты наверняка задаешься вопросом: "А как это ваще работает и почему бы не написать дизассемблер с нуля?". Как бы это не было тривиально - ответ на него ты сможешь получить только осознав, насколько именно тебе сложно написать дизассемблер используя только Intel’овские мануалы на архитектуру процессора. Я лишь кратко рассмотрю принципы кодирования ассемблерных команд, чтобы принцип дизассемблирования был более прозрачным. Каждый байт секции кода твоей программы участвует в формировании той или иной машинной инструкции. Чтобы правильно определить начало следующей команды нужно правильно (как бы это сделал процессор) дизассемблировать предыдущую. Для этого необходимо четко представлять себе формат команд ассемблера. Команды процессора Intel кодируются следующим образом: ![]() При этом единственный обязательный параметр это "код операции", остальные используются в зависимости от сложности и навороченности той или иной команды. Например "преффикс" используется довольно редко, зато префиксы могут поистине творить чудеса над командами, к примеру префикс 66h меняет размерности регистров и адресов при этом в 16 битной программе этот префикс позволяет юзать 32 битные регистры, а в 32 битной - 16 битные. Поля modR/M позволяют определить формат данных, с которыми оперирует программа, будь то регистры, адреса и прочее. Поле SIB расширяет возможности адресации 32 битного режима. Процессор узнает о присутствии этого поля по битам 100b в поле R/M. Далее идут непосредственно смешения и операнды, описанные в структуре modR/M+SIB. Расшифровкой именно этих команд и занимается дизассемблер. Чтобы его написать самому с нуля потребуется море сил и времени. Хотя есть два пути. Наиболее простой из них - составить таблицу опкодов и используя нее дизассемблировать команды (именно этот принцип используется в большинстве дизассемблеров длин и в дельфевом декомпилере DeDe). Второй путь - самый сложный. Он подразумевает полное отсутствие таблиц и использование для дизассемблирования только тех данных что описывают мануалы от авторов процессора. Какой путь выберешь ты - решать только тебе. Хотя предлагаю тебе не париться и использовать уже готовые решения. Об одном из таких решений читай ниже. Выбираем компонент Как ни странно, если очень сильно постараться, можно найти целых два бесплатных дизассемблера, которые можно внедрить в свою прогу на Delphi. Первый можно выдрать из свободно распространяющегося исходника декомпилятора Delphi - DeDe. При желании этот исходник ты можешь взять на www.wasm.ru и самостоятельно его изучить. Мы же рассмотрим второй дизассемблер, поставляющийся в виде компонента для Delphi и бесплатный для некоммерческого использования. Называется данный компонент madDisAsm и входит в состав большой библиотеки компонентов называющейся madCollection. Взять эту коллекцию можно отсюда: http://madshi.bei.t-online.de/madCollection.exe. Теперь когда ты скачал все что нужно давай разберемся, что мы имеем. А имеем мы 8 мощных компонентов, среди которых даже есть madBasic (бешеный барсик :)). Как ты понимаешь, из данного мощного пакета нам потребуется только madDisAsm. Его и рассмотрим. madDisAsm Данный компонент практически недокументирован. Документированы только прототипы функций и структур. Примеров же использования нет ни одного, отсюда следует что разбираться придется самим. Чтобы использовать данный компонент в нашем проекте подключи его в разделе Uses модуля так: uses madDisAsm; Теперь давай рассмотрим функции данного рульного компонента. А основных функции всего две. Первая позволяет дизассемблировать код по команде. При этом все надстройки над кодом ты можешь реализовывать сам в ходе декомпилирования. Прототип функции имеет вид: function ParseCode (code: pointer; var disAsm: string) : TCodeInfo; overload; code - pointer на бинарный код программы, который мы хотим дизассемблировать; disAsm - переменная в которую будет занесена первая дизассемблированная строчка. TCodeInfo - структура дизассемблированной строчки. одним из элементов этой структуры является ссылка на следующую строчку, которую мы можем использовать для цикличного вызова функции. Вот полный прототип данной структуры:
Также для нас будет интересная еще одна функция. Ее особенностью является то, что она способна дизассемблировать всю функцию целиком, автоматически находя конец функции по команде retn. Вот ее прототип:
Тут все аналогично предыдущей функции только в данном случае код будет дизассемблироваться не покомандно а целиком, при этом компонент попытается сам определить ссылки на API и прочие данные, что есть огромный плюс. Структура, возвращаемая данной функцией покруче:
Есть и еще одна третья функция, которая вообще недокументированна:
Насколько я понял эта функция не возвращает структуры, зато дизасмит весь код нужной нам функции и кладет его в переменную disAsm. exceptAddr насколько я понял - это адрес конца дизассемблируемой функции (указывать необязательно), maxLines - число дизассемблируемых строк (если 0, то все), autoDelimiters - точно не могу сказать, но ориентировочно это - завершать ли функцию первым ret’ом или нет. Кодим Теперь, когда мы разобрались с работой компонента давай писать дизассемблер! Открывай Delphi, создавай новый проект, помещай на форму пару Edit’ов, Memo и два CommandButton’а. В результате этих несложных манипуляций мы получим что-то похожее на интерфейс проги (смотри на рисунке). ![]() Теперь самое время осознать для чего будут юзаться два текстовых поля. В первое мы будем вводить имя открываемого для дизассемблирования файла, а во второе - адрес начала дизассемблируемого кода. Так как дизассемблер понимает только pointer’ы на код - напишем функцию которая будет открывать EXE, считывать с указанного смещения код в переменную и возвращать pointer на эту переменную. Собственно функция будет иметь вид:
В данной функции я использовал исключительно Win32 API. Это позволяет добиться максимальной скорости кода и простоты переносимости на другие языки программирования. Теперь напишем функцию, которая будет покомандно дизассемблировать код, pointer на который будет в нее передаваться:
Код перестанет дизассемблироваться, когда программа наткнется на первый ret. Если программа не найдет ret, то она продолжит декомпилировать память до тех пор пока не вызовет ошибку доступа к памяти. Не забудь сделать проверку этого. Теперь, когда основные функции готовы - нам осталось написать только обработчики для кнопок на форме. Код кнопки Dasasm будет выглядеть так:
Теперь, когда дизассемблер готов напишем тестовый проект для его проверки. Пишем тестовый проект Раз мы написали дизассемблер, то самое логичное писать тестовый проект на асме. Самым удобным на мой взгляд редактором и компилятором ассемблера является Fasm, который плюс к своему удобству и простоте очень часто обновляется и версия к версии становится все стабильнее и лучше. Взять его можно на www.wasm.ru. Уже скачал? Тогда запускай его и набирай следующий код:
Компиляем, получаем EXE файл, размером 2 килобайта. Даа, дельфям до ассемблера далеко. ![]() Тестируем Запускай скорее только что написанный дизассемблер. Вводи в одно текстовое поле путь к тестовому проекту а во второе адрес точки входа - 1024 (400h). Для простеньких ассемблерных прог адрес точки входа часто равен смещению секции кода, которое часто равно именно 400h. Жми теперь любую из кнопок декомпилера - ты должен увидеть в Memo код, напоминающий только что написанный тобой, но в более строгом виде:
как видишь все прекрасно работает. ![]() Заключение Надеюсь ты найдешь применение написанному нами дизассемблеру.
|
|
| ||||||||||||||||||||||||||||||||||||||||