Дизассемблер своими руками

     Знание структуры машинных команд уже много лет не является обязательным, для того, чтобы человек мог назвать себя программистом. Естественно так было не всегда. До появления первых ассемблеров программирование осуществлялось непосредственно в машинном коде. Каторжная работа, сопряженная с большим количеством ошибок. Современные ассемблеры позволяют (в разумной степени) абстрагироваться от железа, метода кодирования команд. Что уж говорить о компиляторах высокоуровневых языков. Они поражают сложностью своей реализации и той простотой, с которой программисту позволяется преобразовывать исходный код в последовательность машинных команд (причем преобразовывать, в достаточной степени, оптимально). От программиста требуется лишь знание любимого языка/ IDE. Знание того, во что преобразует компилятор исходный листинг вовсе не обязательно.
Тем же, кому интересно взглянуть на краткое описание структуры кодирования машинных команд, пример реализации и исходный код дизассемблера для x86 архитектуры, добро пожаловать.


     Создание дизассемблера для x86 архитектуры является, хотя задачей и не особо сложной, но все, же довольно специфичной. От программиста требуются определенного рода знания – знания того, как микропроцессор распознает последовательность “байтиков” в машинном коде. Далеко не в каждом вузе можно получить такие знания в объеме достаточном для написания полнофункционального современного дизассемблера – приходится искать самому (как правило, на английском языке). Данный пост не претендует на полноту освещение проблемы создания дизассемблера, в нем лишь кратко рассказывается то, как был написан дизассемблер для x86 архитектуры, 32-разрядного режима исполнения команд. Так же хотелось бы отметить вероятность возможных неточностей при переводе некоторых понятий из официальной спецификации.

     Структура команд для intel x86

Структура команды следующая:
• Опциональные префиксы (каждый префикс имеет размер 1 байт)
• Обязательный опкод команды (1 или 2 байта)
• Mod_R/M – байтик, определяющий структуру операндов команды — опциональный.
• Опциональные байты, занимаемые операндами команды (иногда разделено как один байт поля SIB[Scale, Index, Base], смещения и непосредственного значения).

Префиксы

     Существуют следующие префиксы:
Первые шесть изменяют сегментный регистр, используемый командой при обращении к ячейке памяти.
• 0x26 – префикс замены сегмента ES
• 0x2E – префикс замены сегмента CS
• 0x36 – префикс замены сегмента SS
• 0x3E – префикс замены сегмента DS
• 0x64 – префикс замены сегмента FS
• 0x65 – префикс замены сегмента GS

• 0x0F – префикс дополнительных команд (иногда его не считают за настоящий префикс – в этом случае считается, что опкод команды состоит из двух байт, первый из которых 0x0F)

• 0x66 – префикс переопределения размера операнда (к примеру, вместо регистра eax будет использоваться ax)
• 0x67 – префикс переопределения размера адреса (см ниже)
• 0x9B – префикс ожидания (WAIT)
• 0xF0 – префикс блокировки (LOCK с его помощью реализуется синхронизация многопоточных приложений)
• 0xF2 – префикс повторенья команды REPNZ – работа с последовательностями байт (строками)
• 0xF3 – префикс повторенья команды REP – работа с последовательностями байт (строками)

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

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

Байт Mod_R/M состоит из следующих полей:

• Mod – первые два бита (значение от 0 до 3)
• R/M – следующие три бита (значение от 0 до 7)
• Value of ModR/M – следующие три бита (значение от 0 до 7)

     Реализация:

Для написания дизассемблера мы будем использовать следующую страничку: http://ref.x86asm.net/geek32.html.

     Мы видим несколько таблиц. В сущности, только эти таблицы и описание их полей нам и понадобятся, для написания дизассемблера. Конечно, дополнительно требуется способность к логическому рассуждению и свободное время.
     В первой таблице представлен список машинных команд, не содержащих префикс 0x0F. Во второй список команд содержащих этот префикс (большинство этих команд появились в микропроцессорах семейства “Pentium with MMX” или более поздних).
     Следующие три таблицы позволяют преобразовать байт Mod_R/M в последовательность операндов команды для 32-битного режима кодировки команд. Причем каждая последующая из этих трех таблиц уточняет разбор Mod_R/M байта частных случаев предыдущей таблицы.
     Последняя таблица позволяет преобразовать байт Mod_R/M в последовательность операндов команды для 16-битного режима кодировки команд. По умолчанию считается, что команда кодируется в 32-битном режиме. Для смены режима кодировки используется префикс переопределения размера адреса (0x67).

     Первое, что необходимо сделать, это перенести первые две таблицы в удобные для работы структуры данных. На том же сайте можно скачать xml-версии данных таблиц, и уже их преобразовать в красивые сишные структуры. Я же поступил иначе – загрузил html таблицы в Excel, и уже там, написав несложный скриптик на VBA, получил исходный сишный код, который, уже после ручных исправлений представлял собой требуемые структуры данных.

Сам алгоритм дизассемблирования достаточно прост:

• Собирается список префиксов, используемых в текущей машинной инструкции
• Ищется в одной из двух таблиц соответствующее поле в зависимости от опкода, префиксов и поколения (модели) целевого (искомого) микропроцессора.
• Найденная нами запись характеризуется списком полей такими как поколение (модель) микропроцессора, с которого появилась поддержка данной команды или, например, список флагов, которые данная команда может изменить. Нас же, в основном, интересуют лишь мнемоника (название) команды и список операндов. Проанализировав все операнды найденной и поля байта Mod_R/M, мы сможем узнать текстовое представление и длину команды.

     Количество операндов может колебаться от нуля до трех. Исходные таблицы содержат более сотни типов операндов. Некоторые операнды дублируются – у них различные названия, но последовательность действий обработки Mod_R/M байта (и возможно последующих байтов) у них одинакова.

     Для просмотра примера обработки различных операндов и примера дизассемблирования простейшей функции “Hello world” можно скачать исходный код дизассемблера для компилятора C++ Builder 6.

PS:
Не факт, что кому-то из прочитавших этот пост, когда-либо понадобится информация, почерпнутая из него (дизассемблеры пишут единицы), но, в любом случае этот дизассемблер тестировался и даже входит в состав достаточно большего коммерческого протектора, исходники открыты и распространяются свободно )
Поделиться публикацией
Ой, у вас баннер убежал!

Ну. И что?
Реклама
Комментарии 41
  • +1
    Была у меня такая книжка: Assembler. Специальный справочник. Всё собрано в кучу и даже на русском языке. В этой книге прилагалось описание команд и соответствующие им коды.
    • 0
      Первое, что я читал — В. Юров «Assembler» — да еще Криса Касперски статьи (своеобразно общается на rsdn'е)!
      • +1
        Мыщъх до сих пор статьи пишет? А где? :-)
      • 0
        А я начал с «Пильщикова — Ассемблер» и «Зубкова — Ассемблер для DOS, Windows и UNIX».
        Вторая вообще мастхэв. Сейчас вроде 3е издание вышло. я еще 1е покупал…
        • 0
          Юров был пожалуй первым, ну может наравне с Пироговым. Затем была шиза писать ОС на ассемблере и хорошо что не начал сам, потому что тогда Колибри могло бы и не быть. :-)
        • 0
          В своё время заказал набор книг на английском «Intel 64 and IA-32 Architectures Software Developer's Manual» (5 книг, из которых 2 — Instruction Set) пришли абсолютно бесплатно. Заказал тогда же ещё один комплект — подарил знакомой. Когда другу захотелось тоже — дал ссылку, он заказал, но пришел диск (видимо слишком много народу заказывало, ибо ссылка была уже на всех сайтах халявы).

          А свежая версия всегда доступна в электронном виде на сайте

          P.S. Нашел даже статью про эти книги habrahabr.ru/blogs/asm/28491/
          P.P.S В электронном виде книги всегда в актуальном виде и содержат все исправления и дополнения (в том числе касающиеся новых процессоров).
          • 0
            Жаль халява закончилась. А сейчас можно где-то их приобрести в бумаге?
            • 0
              Похоже, что только если самому печатать, ну или поискать кого-нибудь, у кого есть, но не нужны, только те книги будут старой версии и не будут содержать ничего о процах, вышедших позже Atom'а.

              Если найти достаточное количество людей, которым нужны в печатном виде, то можно было бы заказать на какой-нибудь типографии небольшой тираж (беглый гуглёж показал, что типографии печатают тиражами от 100/200/500 экземпляров книг в мягком переплёте). Так что если будет востребовано, то можно собрать народ и заказать.
        • 0
          >До появления первых ассемблеров программирование осуществлялось непосредственно в машинном коде.

          Интересно, действительно ли так было. Лет 20 назад «компилировал» команды ассемблера (i8080, а вернее КР580ВМ80А) в машкод в тетрадке, а потом вводил хекс-дампы. Мог, конечно, без тетрадки написать «Hello World», но не более. Но как-то сомневаюсь, что даже первые программисты не использовали какие-то мнемокоды, а кодили прямо в хексебине.
          • +1
            Не буду вступать в «священные войны» — пост был о времени 40-50 лет назад! (сам его не застал — увы ((( )
            • 0
              Я это понял и воевать не собирался. Мне интересно, действительно ли были люди переводящие алгоритмические абстракции в машинные коды без каких-то мнемоник, то есть формирование образа кода типа: «заносим в первый регистр процессора адрес строки» в виде: «21 07 01» без формирования этого образа.
              • 0
                В той же тетрадке переводили же как-то мнемонику в хекс дамп.
                • 0
                  чаще удобнее было использовать восьмеричную систему — тогда опкоды многих категорий команд выглядят стойно и логично: (абстрактный пример) 01.010.001(bin) -> 121(oct) значит что-нибудь вроде ld B,A. Старшие два бита — тип команды, следующие 3 бита — регистр-получатель, следующие три — исходный регистр.

                  в таком виде при определённой сноровке вполне можно было оперировать опкодами, без мнемоник.
                  • +1
                    Это кстати, да, завидовал в свое время обладателям БК-0010-01, в том числе и из-за восьмеричной системы по дефолту.
                  • 0
                    Держа под рукой таблицу (16х16) перевода мнемоники в команды.
              • 0
                У моего друга был советский компьютер (Вектор или Поиск, за давностью лет не помню), аналог PC XT, но без дисковода и жесткого диска. Была только возможность записывать и читать программы с магнитофона. Из языков программирования был только встроенный бейсик. Ему удалось на время раздобыть книгу по ассемблеру 8086, переписал все команды и коды в тетрадку, и написал в машинных кодах графический редактор (аналог Paint'а, хранившегося в ПЗУ Yamaha, которые стояли у нас в школе)…

                Мы тогда, кажется, в 7-ом классе учились. Вот такой супер-мозг. Один раз было достаточно прочитать книгу, чтобы написать в кодах редактор. После школы потерял контакт, к сожалению, разъехались в разные города.
              • 0
                Еще такой глупый вопрос к хабр-сообществу, ответить на вопрос — знаете ли вы (без гугла), сколько и какие байты составляют минимально-возможный цикл для x86 архитектуры (!!! пожалуйста отвечайте только да или нет)
                • +16
                  А знаете ли вы без гугла, как выглядит байткод оптимизации хвостовой рекурсии в CLR на 64-битной архитектуре? Пожалуйста, отвечайте только зачем нам нужно знать про байтовое представление минимально возможного цикла на x86
                  • +6
                    Уважаемое хабросообщество, считаю, что нам сейчас же стоит вернуть этому парню карму (когда-то несправедливо отнятую). Почитайте его комменты, прошу вас. Это же чистейший случай, когда человек в полном адеквате, генерирует отличный контент и при этом почему-то ходит с отрицательной кармой (что несколько мешается, согласитесь). В рамках проекта по возвращению адекватов на Хабру, предлагаю привести в порядок ситуацию. Кто-то же должен из старожилов помнить пост с погребенными хабровчанами, это тот случай, блин.
                    • +2
                      Присоединяюсь! Помог, чем смог… :-)
                      • +3
                        Плюсанул карму из уважения к вам лично (его коммент по прочтению), но потом стало интересно за что — почти чистейший случай, но почти — ненормативную лексику иногда употребляет и потому, имхо, не поддержу мнение, что в полном адеквате… как и я :)
                      • 0
                        > А знаете ли вы без гугла, как выглядит байткод оптимизации хвостовой рекурсии в CLR на 64-битной архитектуре?

                        Увы нет, ибо фанатик нэйтевного интеловского кода (а виртуальных машин много)

                        > Пожалуйста, отвечайте только зачем нам нужно знать про байтовое представление минимально возможного цикла на x86

                        Да, конечно не нужно — я в посте об этом писал — интересно много-ли знают ;)
                      • НЛО прилетело и опубликовало эту надпись здесь
                        • 0
                          да, и чуть ниже его написали)
                          • 0
                            Ну если так то и renpz movsb например тоже за цикл сойдет :)
                        • +2
                          Я использую 0xeb 0xfe для того, чтобы зациклить программу.
                          • 0
                            Нет.

                            Точно не знаю, но, по аналогии с i8080, — один байт: HLT (0x76) если память не изменяет. Гуглить формулировка вопрсоа не позволила :(
                          • +1
                            Кстати, достаточно большой коммерческий протектор не очень хорошо защищен от взлома:
                            • +1
                              Есть две большие разницы
                              1) Программа зарегистрирована
                              2) Программа говорит, что она зарегистрирована

                              Может напишете подробности?!
                            • 0
                              Хм, для дизасма не проще скачать официальную документацию по процам, она вся открытая, надо только поискать на сайте Intel, там все досконально расписано. Сам по их доке писал компилятор, дока идеальна :-)
                            • 0
                              исподники открыты и распространяются свободно

                              Скажите, а что такое исподники? Это то что одевают при ходьбе в исподнем?
                            • 0
                              • 0x3E – префикс замены сегмента BS
                              Это у вас что-за сегмент такой? В каком процессоре он появился?
                              • 0
                                Причем «В» на русском написал) — поправил.
                              • +3
                                Насколько же все-таки покалеченная в x86 система команд…
                                • 0
                                  IDA.
                                  • 0
                                    Все же больше Ольга, хотя и за этой программой я провел не одну ночь.
                                  • 0
                                    Есть отличная статья Криса Касперски «Дизассембилрование в уме» , которая раскрывает все тонкости дизассемблирования.

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                    Самое читаемое