|
Выпуск 7 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Доброе время суток, уважаемые читатели! Выпуск этой рассылки по времени совпал с праздником 8 марта, поэтому пользуясь случаем, хочу поздравить с этим замечательным праздником прекрасную половину читателей этой рассылки. Хочу пожелать вам здоровья, любви, хорошего настроения, молодости и красоты!
Наконец, этот выпуск увидел свет. Кстати, это первый выпуск в этом году. На самом деле, материала накопилось много, но вот излагать его хотя бы также аккуратно как раньше, не успеваю. Посмотрим, как будет складываться судьба рассылки в дальнейшем. Теперь у рассылки появился сайт! И адрес у него вот такой - http://tmc.sf.net/ . Мне его очень давно не хватало, но вот создать его толком было некогда. Он и сейчас нуждается в серьезной доработке, как в плане дизайна и логической структуры, так и в плане наполнения материалом. По крайней мере, теперь есть где разместить выпуски рассылки и сопутствующие им материалы. Тематику сегодняшнего выпуска вначале планировалась выбрать несколько по-другому, но потом я решил, что лучше вначале логически завершить программную часть. В сегодняшнем выпуске мы поговорим о дизассемблере. Что же такое дизассемблирование?Вначале кратко о том, что собой представляет дизассемблирование. Как следует из названия, этот процесс обратный ассемблированию. Поэтому не сильно погрешив против истины, можно считать, что дизассемблирование - исполнение алгоритма, который получает на входе бинарный исполнимый код, а на выходе выдает его мнемоническую запись на языке ассемблера. При этом важно отметить, что далеко не всегда получаемый текст совпадает с исходным ассемблерным. Как правило, дизассемблер совершенно не в состоянии восстановить названия меток или точно команду. С названиями меток все просто, зачастую они отсутствуют в бинарном файле, потому что пересчитываются непосредственно в адреса, и необходимость в них отпадает. Что же касается восстановления команд, то это менее критично. Например, в семействе микропроцессоров x86 компании Intel для машинной инструкции 90h используются два мнемонических обозначения "NOP" и "XCHG AX,AX" (см. Intel Architecture Software Developer's Manual, volume 2: Instruction Set Reference, p. 340) Впрочем, могут встретится более тонкие и важные моменты. Например, Kris Kaspersky в статье "Тонкости дизассемблирования" указывает на следующие особенности:
Это фрагмент следует отметить не только как иллюстрацию собственно к особенностям дизассемблирования, а также как пример несовпадения работы реального и виртуального устройства. Важно помнить, что обычно существуют недокументированные возможности. При создании эмулятора это следует учитывать. Впрочем, чтобы предусмотреть все нюансы потребуется очень много времени на изучение реального поведения устройства (составление тестов, моделирование исключительных ситуаций и пр.), стоит ли его тратить с этой целью, полностью зависит от требований, предъявляемых к эмулятору. Напоследок, замечу, что дизассемблер очень часто применяется с целью внести изменения в уже написанное приложение, исходные тексты которого недоступны. Между прочим, не обязательно на языке ассемблера, ведь дизассемблер может запросто обрабатывать бинарный файл, который был получен компилятором с языка высокого уровня. Однако, часто такое вмешательство незаконно и преследуется во многих странах. Мы же преследуем учебные цели. И я утверждаю, что дизассемблер - инструмент не только и не столько Cracker-а. Это учебный инструмент. Экскурс в историю созданияВ процессе работы над TMC 1, параллельно с разработкой ассемблера было решено создать дизассемблер для автоматического тестирования первого. Идея вынашивалась долго и была реализована в течение суток. Вообще-то говоря, хронологически дизассемблер относится ко времени TMC 2 и написан на языке Си++. Однако, он разработан под архитектуру TMC 1 и полностью зависим от той спецификации. Дизассемблер создавался тогда, когда не был готов сам ассемблер. Он, кстати, и сейчас не готов полностью и не уверен, что будет вообще доведен до конца. По крайней мере тем человеком, который им занимается. Отчасти это моя вина, потому что архитектура команд довольно путаная и плохо документирована. Возможно, под новую архитектуру он будет получаться проще и я сам его напишу за пару дней :). Процесс этот был настолько увлекательный и стремительный, что у меня толком не осталось черновиков с собственными комментариями и подпрограммами ПЗУ. Восстановить тексты мне помог мой коллега по цеху (и участник KMS) - Flash (e-mail: birya at km.ru, FidoNet: 2:5061/52.777). Эти тексты служат отличной иллюстрацией ассемблера. Для всех, кто знаком с Intel x86 все должно быть предельно понятно. На сайт выложен черновик документации, которая использовалась при разработке ассемблера. Она может пояснить особенности кодирования инструкций. Также на сайте есть таблицы, необходимые для кодирования команд. На любые вопросы тех, кто отважится покопаться в этих спецификациях, я обязательно отвечу. Это позволит прояснить реальную ситуацию с документированием. Раскопки Flash-аПервый фрагмент представляет собой начальную загрузку. На него передается управление при старте машины. Смысл пока нас не будет интересовать. Основное - уловить типы команд и выполняемые каждой действия. -------------------------------------------------- Номер фрагмента: 1 Смещение в файле: 0x0000 (0) - 0x0084 (132). Длина фрагмента: 133 байта. -------------------------------------------------- c000 mov WORD AR7, c000 c004 mov WORD [1fe], ef00 c00a mov WORD [40], c100 c010 mov BYTE R00, fe c013 in BYTE R00, 30 c016 sti c017 nop c018 nop c019 nop c01a nop c01b nop c01c nop c01d nop c01e nop c01f nop c020 mov BYTE R01, 0 c023 in BYTE R01, 40 c026 out WORD R0, 41 c029 inc WORD R0 c02b in BYTE R01, 40 c02e in WORD R0, 41 c031 mov WORD [200], R0 c035 nop c036 nop c037 nop c038 nop c039 nop c03a nop c03b nop c03c nop c03d nop c03e nop c03f nop c040 mov WORD AR0, cf00 c044 mov WORD AR1, f000 c048 mov WORD R7, 0 c04c int BYTE ff c04e mov WORD AR0, d000 c052 mov WORD AR1, f190 c056 int BYTE ff c058 mov WORD AR1, f1a7 c05c mov WORD R7, 1 c060 int BYTE ff c062 mov WORD AR0, d020 c066 mov WORD AR1, f1b8 c06a mov WORD R7, 0 c06e int BYTE ff c070 mov WORD AR1, f1c4 c074 mov WORD R7, 1 c078 mov WORD R7, [AR0] c07a add WORD [AR1], [AR0] c07c int BYTE ff c07e cmp WORD R0, 220 c082 jb/jnae/jc BYTE f4 c084 hlt На распечатке четыре колонки. Первая - адреса инструкций в памяти. После включения питания, управление передается на адрес 0C000h. Вторая колонка - мнемоническое обозначение инструкции процессора, третий - показывает размер операнда, а четвертый сами операнды. Инструкции mov, вероятно не нуждаются в пояснении. Их смысл - пересылка данных. Инструкция in совершенно типична для x86. Это чтение из порта ввода-вывода. STI - устанавливает флаг разрешения обработки прерываний. Hlt - останавливает работу процессора. Int - вызов программного прерывания. Add - сложение, cmp - сравнение, jb/jnaе/jc - условные переходы. Этот фрагмент с условным переходом показывает еще одну неоднозначность при дизассемблировании. Дело в том, что все эти команды выполняют одинаковые действия. Для удобства чтения и записи можно использовать Jump if Below (jb) или Jump Not Above or Equal (jnae) или Jump If Carry (jc), т.е. то название, которое больше подходит по смыслу в этот алгоритм. Фактической же разницы между командами нет никакой, ведь можно записать как "a<b", так и "not (a>=b)". Совершенно эквивалентная запись. Собственно, этот фрагмент и есть весь основной код программы демонстрации возможностей. Вся основная работы скрыта в функциях прерывания 255 (0FFh). Они представляют некий аналог программных прерываний ПЗУ. Например, в IBM PC прерывание 10h предоставляет функции для управления выводом, 13h - работу с дисками, 16h - ввод с клавиатуры и т.д. А вот этот один из немногих черновиков, которые остались еще при разработке. Вначале я написал программу на ассемблере, а затем справа вручную соущствил ее ассемблирование. Подобным образом происходило написание всего ПЗУ. Этот фрагмент - преобразование числа из регистра r0 в строку, заданную регистром ar0: -----¬ ¦2100¦ L----- push ar0 41 81 push ar1 41 83 push r0 41 80 push r1 41 82 mov r1,r0 A1 82 shr r0,12 C4 80 0C mov ar0,0EFF0h A0 81 F0 EF add ar0,r00 01 01 movb [ar1],[ar0] A3 02 inc ar1 49 03 sub ar0,r00 E1 01 mov r00,r11 A1 30 and r00,0Fh 10 00 0F add ar0,r00 01 01 mov [ar1],[ar0] A3 02 inc ar1 49 03 sub ar0,r00 E1 01 mov r00,r10 A1 20 shr r00,04 C4 00 04 add ar0,r00 01 01 movb [ar1],[ar0] A3 02 inc ar1 49 03 sub ar0,r00 E1 01 mov r00,r10 A1 20 and r00,0Fh 10 00 0F add ar0,r00 01 01 movb [ar1],[ar0] A3 02 inc ar1 49 03 movb [ar1],0 A0 42 00 pop r1 44 82 pop r0 44 80 pop ar1 44 83 pop ar0 44 81 iret 27 Надеюсь, что из этих примеров станет примерно понятен спектр инструкций, которые необходимо дизассемблировать. Тем, кто вообще ничего не понял, могу только посочувствовать, чтение курса ассемблера x86 (и для TMC 1) не входит в мои планы. К сожалению, на это совсем нет времени. Впрочем, может кто-то сможет внести предложение по поводу способа строгого изложения описания инструкций. Программирование дизассемблераПриводить в рассылке исходный текст дизассемблера не имеет смысла, потому что он довольно большой и технически сложный. Вместо этого вот ссылка [7kb] на него. Кстати, в тексте программы присутствуют основные комментарии. Здесь хотелось бы сказать несколько слов о его сборке и использовании. Выполнен дизассемблер как консольное приложение и написан с учетом ISO стандарта языка Си++ 98-го года (14882), поэтому должен компилироваться всеми компиляторами, которые поддерживают этот стандарт. Проверялось с GNU C++ 2.95 и MS Visual C++ 6.0 . Весь интерфейс с пользователем ведется через консоль и должен быть понятен, потому что построен на принципе вопрос-ответ. Благодаря такой организации программы удается выполнять скрипты. Вот пример script.txt и пояснение его формата: 1: 2 ; Это пункт меню. Должно всегда быть так. 2: tmc_1_0.rom ; Это имя файла с ПЗУ, из которого и будет ; происходить дизассемблирование. 3: 0 ; С какой позиции файла производить ; дизассемблирование. 4: 20 ; Размер дизассемблируемого кода в байтах. 5: 49152 ; Это адрес C000 (в дес. форме) - реальный адрес, ; по которому ; загрузится дизассемблируемый фрагмент. Удобен ; для документирования. 6: quit ; Выход из программы и конец скрипта. Запускать надо так: TMC_1_0_Disasm.exe Спасибо за внимание. С уважением, автор рассылки: Виктор Петренко (Top)
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||