рефераты конспекты курсовые дипломные лекции шпоры

Реферат Курсовая Конспект

Структура команд INTEL 80х86

Структура команд INTEL 80х86 - раздел Компьютеры, NAG SCREEN "— Потому Ты И Опасен, Что Ояладел Своими Страстями." Ф. Хер...

"— Потому ты и опасен, что ояладел своими страстями." Ф. Херберт. "Мессия Дюны".

Дизассемблирование (тем более в уме) невозможно без понимания того, как процессор интерпретирует команды. Конечно, можно просто запомнить все опко-ды команд и записать их в таблицу, которую потом вызубрить наизусть, но это не самый лучший путь. Уж слишком много придется зубрить. Гораздо легче понять, как строятся команды, чтобы потом с легкостью манипулировать ими.

Для начала разберемся с форматом инструкций архитектуры Intel:


Префикс | Опкод | ModR / М | SIB | Смещение | Непосредств, операнд

       
   

Mod | Reg / Opcode | R/M | | Scale | Index | Base |


Заметим, что все поля, кроме поля опкода, являются факультативными. Т.е. в одних командах они могут присутствовать, а в других нет.

Само поле опкода занимает восемь бит и часто (но не всегда) имеет следую­щий формат:

Опкод Направление Размер
  Регистр
  Условие Инверсия

7 4 3 2 1 0

Поле размера равно нулю, если операнды имеют размер в байт. Единичное значение указывает на слово (двойное слово в 32-режиме или с префиксом 0х66 в 16-разрядном режиме).

Направление обозначает операнд-приемник. Нулевое значение присваивает результат правому операнду, а единица левому. Рассмотрим это на примере инструкции mov bx.dx:

8BDA mov bx,dK

lOOOlO11b

69DA mov dx, bx

lOOOlOO1b

He правда ли, мы можем как по мановению волшебной палочки менять местами операнды, меняя всего один бит? Однако задумаемся, как это поле будет вести себя, когда один из операндов имеет непосредственное значение? Разуме­ется, оно не может быть приемником и независимо от значения этого поля будет только источником. Инженеры Intel учли такую ситуацию и нашли оригинальное применение, часто экономящее до трех байтов. Рассмотрим ситуацию, когда операнду размером в слово или двойное слово присваивается непосредственное значение, меньшее по модулю 0х100. Ясно, что значащим является только младший байт, а нули, стоящие слева, по правилам математики можно и отбросить. Но попробуйте объяснить это процессору! Нужно пожертвовать хотя бы одним битом, чтобы указать ему на такую ситуацию. Вот тут-то и используется байт направления. Рассмотрим следующую команду:

810623016600 add w,[00123],066

|—> 10000001

Если теперь флаг направления установить в единицу, то произойдет следующее

8306230166 add w,[00123],066

|-à 10000011

Таким образом, мы экономим один байт в 16-разрядном режиме и целых три в 32-разрядном. Этот факт следует учитывать при написании самомодифицирую­щегося кода. Большинство ассемблеров генерируют второй (оптимизированный) вариант, и длина команды оказывается меньше ожидаемой. На этом, кстати, основан очень любопытный прием против отладчиков. Посмотрите на следующий пример:

00000100: 810600010200 add w,[00i00] ,00002

00000106: В^Об mov ah, 006

00000108: B207 mov dl, 007

0000010A: CD21 int 021

0000010C: C3 retn

После выполнения инструкция в строке 0х100 приобретет следующий вид:

00000100: 8206000102 add b, [00100],002

00000105: 00B406B2 add [si][OB206],dh

|

----------ip

Т.е. текущая команда станет на байт короче! И "отрезанный" ноль теперь — часть другой команды! Но при выполнении на "живом" процессоре этого не произойдет, так как следующее значение ip вычисляется еще до выполнения команды на стадии ее декодирования.

Совсем другое дело — отладчики, и особенно отладчики-эмуляторы, которые часто вычисляют значение ipпосле выполнения команды (это легче запрограм­мировать). В результате наступает крах. Несущественное, казалось бы обстоя­тельство — до или после выполнения команды вычисляется ip — оказалось роковым. Приведу в подтверждение дамп экрана:

Заметим, что этот прием может быть бессилен против трассирующих отлад­чиков (Debug.com, DeGlucker, Cup386), поскольку значение ip за них вычисляет процессор и вычисляет его правильно.

Однако на "обычные" отладчики управа всегда найдется (см. гл. "Приемы против отладчиков реального режима"), а с эмуляционными справиться гораздо труднее и приведенный пример один из немногих, где обеспечивается воздействие на виртуальный процессор.

Перейдем теперь к рассмотрению префиксов. Они делятся на четыре группы:

1, Префиксы блокировки и повторения:

• OxFO LOCK-префикс;

« OxF2 REPNZ (только для строковых инструкций);

• OxF3 REP (только для строковых инструкций).

2. Префиксы переопределения сегмента:

« Ох2Е CS:

« 0х36 SS:

' ОхЗЕ DS:

« 0х26 ES:

« 0х64 FS:

. 0х65 GS:

3. Префикс переопределения размеров операндов:

«0х66

4. Префикс переопределения размеров адреса:

«0х67

Если используется более одного префикса из той же самой группы, то действие команды не определено и по-своему реализовано на разных типах процессоров.

Префикс переопределения размера операндов используется в 16-разрядном режиме для манипуляции с 32-битными операндами и наоборот. При этом он может стоять перед любой командой, например 0х66 : CLI будет работать! А почему бы и нет? Интересно, но отладчики этого не учитывают и отказываются работать. То же относится к к дизассемблерам, к примеру IDA:

seg000:0100 start proc near

seg000:0100 db 66h

seg000;0100 cli

seg000:0102 db 67h

seg000:0102 sti

seg000:0104 retn

На этом же основан один очень любопытный прием противостояния отладчи­кам, в том числе и знаменитому отладчику-эмулятору cup386. Рассмотрим, как работает конструкция 0х66 : RETN. Казалось бы, коль скоро функция retn не имеет операндов, то префикс 0х66 можно просто игнорировать. На самом деле все не так просто. Retn работает с неявным операндом — регистром ip/eip. Именно это и изменяет префикс. Разумеется, в реальном и 16-разрядном режиме указатель команд всегда обрезается до 16 бит, и поэтому на первый взгляд возврат сработает корректно. Но стек-то окажется несбалансированным! Из него вместо одного слова взяли целых два! Так нетрудно получить и исключение ОхС — исчерпание стека. Попробуйте отладить чем-нибудь пример cracklE.com — даже сир386 во всех режимах откажется это сделать, a Tui'bo-De-buger вообще зависнет! IDA не сможет отследить стек и вместе с ним все локальные переменные.

Как видим, прием крайне прост, но и крайне надежен. Впрочем, следует признать, что перехват int ОхС под операционной системой Windows бесполезен и, несмотря на все ухищрения, приложение, породившее такое исключение, будет безжалостно закрыто. Хотя в реальном режиме это работает превосходно. Попро­буйте убедиться в этом па примере cracklE.com. Забавно наблюдать реакцию на него эмулирующих отладчиков. Все они либо неправильно работают (снимают одно слово из стека, а нс два), либо совершают очень далекий переход по 32-битному eip (в результате чего виснут), либо — чаще всего — просто аварийно прекращают работу по исключению ОхС (так ведет себя сир386).

Еще интереснее получится, если попытаться исполнить в 16-разрядном сег­менте команду CALL. Если адрес перехода лежит в пределах сегмента, то ничего необычного ожидать не приходится. Инструкция работает нормально. Чудеса начинаются, когда адрес выходит за эти границы. В защищенном 16-разрядном режиме при уровне привилегий CLO с большой вероятностью регистр EIP "обрежется" до шестнадцати бит и инструкция сработает (но похоже, что не на всех процессорах). Если уровень не CLO, то генерируется исключение защиты O.xD. В реальном же режиме эта инструкция может вести себя непредсказуемо. Хотя в общем случае должно генерироваться прерывание int OxD. В реальном режиме его нетрудно перехватить и совершить далекий 'Гаг* переход в требуемый сегмент. Так поступает, например, моя собственная операционная система OS7R, дающая в реальном режиме плоскую память. Разумеется, такой поворот событий не может пережить ни один отладчик. Ни трассировщики реального режима, ни v86, ни protect-mode debuger, ни даже эмуляторы (во всяком случае те, что мне известны), с этим справиться не в состоянии.

Одно плохо — все эти приемы не работают под Windows и другими операци­онными системами. Это вызвано тем, что обработка прерываний типа исключения общей защиты всецело лежит на ядре операционной системы и оно не позволяет приложениям распоряжаться им по своему усмотрению. Забавно, что в режиме эмуляции MS-DOS некоторые EMS-драйверы ведут себя в этом случае совершен­но непредсказуемо. Часто при этом они не генерируют ни исключения ОхС, ни OxD. Это следует учитывать при разработке защит, основанных на приведенных выше приемах.

Обратим внимание также на последовательности типа 0х66 0х66 [ххх]. Хотя фирма Intel не гарантирует поведение своих процессоров в такой ситуации, но фактически все они правильно ее интерпретируют. Иное дело некоторые отлад­чики и дизассемблеры, которые спотыкаются и начинают вести себя некорректно.

Есть еще один интересный момент, связанный с работой декодера микропро­цессора.

F E D C B A 9 8 7 6 5 4 3 2 1

 

Декодер

Декодер за один раз считывает только 16 байт, и если команда "не уместится", то он попросту не сможет считать "продолжение" и сгенерирует общее исключе­ние защиты. Однако иначе ведут себя эмуляторы, которые корректно обрабаты­вают "длинные" инструкции.

Впрочем, все это очень процессорно-зависимо. Никак не гарантируется со­хранность и преемственность работы в будущих моделях. Поэтому и злоупотреб­лять этим не стоит. Иначе ваша защита откажет в работе.

Префиксы перекрытия сегмента могут встречаться перед любой командой, в том числе и не обращающейся к памяти, — например, CS:NOP вполне успешно выполнится. А вот некоторые дизассемблеры сбиться могут. К счастью, IDA к ним не относится. Самое интересное, что комбинация из DS:FS:FG:CS:MOV дх, (100]

работает вполне нормально (хотя не гарантируется фирмой Intel). При этом последний префикс в цепочке перекрывает все остальные. Некоторые отладчики, наоборот, ориентируются на первый префикс в цепочке, что дает неверный результат. Этот пример хорош тем, что великолепно выполняется под Windows и другими операционными системами. К сожалению, на декодирование каждого префикса тратится один такт и все это может медленно работать.

Вернемся к формату опкода. Выше была описана структура первого байта. Отметим, что это фактически недокументировано и Intel уделяет ему всего два слова. Действительно, формат команд разнится от одной команды к другой. Однако можно выделить и некоторые общие правила.

Практически для каждой команды, если регистром-приемником фигурирует AX (AL) существует специальный однобайтовый опкод, который в трех младших битах содержит регистр-источник. Этот факт следует учитывать при оптимиза­ции. Так, среди двух инструкций XCHG АХ,ВХ и XCHG BX.DX следует всегда автоматически выбирать первую, так как она на байт короче. (Кстати, инструкций XCHG АХ,АХ более известна нам как NOP. О достоверности этого факта часто спорят в конференциях, но на странице 340 руководства 24319101 фирмы Intel об этом сказано недвусмысленно. Выходит, никто из многочисленных спорщиков не знаком даже с оригинальным руководством производителя.)

Для многих команд (Jx) четыре младшие бита обозначают условие операции. Точнее говоря, условие задается в битах 1-2-3, а младший бит приводит к его инверсии.

Код Мнемоника Условие
Переполнение
B, NAE Меньше
Z Равно
оно BE, NA Меньше или равно
S Знак
P. РЕ Четно
L, NGE Меньше (знаковое)
LE, NO Меньше или равно (знаковое)

Как видим, условий совсем немного, — чтобы никаких проблем их запомина­ния не возникало. Теперь уже не нужно мучительно вспоминать: 'JZ' — это 0х74 или 0х75. Так как младший бит первого равен нулю, то jz это 0х74, a jnz соответственно 0х75.

Далеко не все опкоды смогли поместиться в первый байт. Инженеры Intel задумались о поиске дополнительного места для размещения еще нескольких бит н при этом обратили внимание на байт modR/M. Подробнее он описан ниже, а пока рассмотрим приведенный выше рисунок.

Трехбитовое поле reg, содержащее регистр-источник, очевидно, не использу­ется, когда вслед за ним идет непосредственный операнд. Так почему бы его не использовать для задания опкода? Однако требуется указать процессору на такую ситуацию. Это делает префикс OxF, размещенный в первом байте опкода. Да, именно префикс, хотя документация Intel этого прямо и не подтверждает. При этом на не ММХ-процессорах для его декодирования требуется дополнительный такт. Intel же предпочитает называть первый байт основным, а второй уточняю­щим опкодом. Заметим, что то же поле используют многие инструкции, опериру­ющие с одним операндом (jmp, call). Это все очень сильно затрудняет написание собственного ассемблера/дизассемблера, но зато дает простор для самомодифи­цирующегося кода и, кроме того, вызывает уважение к инженерам Intel, до минимума сократившим размеры команд. Конечно, это далось весьма не просто. И далеко не все дизассемблеры работают правильно. С другой стороны, именно благодаря этому и существуют успешно противостоящие им защиты.

Чтобы избежать этого, нужно четко представлять себе сам принцип кодировки команд, а не просто "мертвую" таблицу опкодов, которую многие вводят в дизассемблер и на этом успокаиваются. Ведь внешне все работает правильно.

К тонкостям кодирования команд мы вернемся ниже, а пока приготовимся к разбору поля modR/M. Два трехбитовых поля могут задавать код регистра общего назначения по следующей таблице:

  8 бит операнд 16 бит операнд 32 бит операнд
Код AL AX EAX
  CL CX ECX
  DL DX EDX
  BL BX EBX
  AH SP ESP
  CH BP EBP
  DH SI ESI
  BH Dl EDI

Опять можно восхититься лаконичностью решения инженеров Intel, которые ухитрились закодировать столько регистров всего в трех битах. Отсюда, кстати, становится 'ясно, почему нельзя выборочно обращаться к старшим и младшим байтам регистров SP, BP, SI, DI и, аналогично, старшему слову всех 32-битных регистров. Во всем "виновата" оптимизация и архитектура команд. Просто нет свободных полей, в которые можно было бы "вместить" дополнительные регист­ры. Сегодня мы вынуждены расхлебывать результаты архитектурных решений, выглядевших такими удачными всего лишь десятилетие назад.

Обратите внимание на порядок регистров AX-CX-DX-BX-SP-BP-SI-DI. Алфа­витный порядок немного нарушен, верно? Особенно странно в этом отношении выглядит BX. Но если понять причины, то не будет никакой нужды запоминать это исключение, все станет на свои места. BX — это индексный регистр. И стоит первым среди индексных.

Таким образом, мы уже можем "вручную", без дизассемблера, распознавать в шестнадцатиричном дампе регистры-операнды. Очень неплохо для начала! Или писать самомодифицирующийся код. Например:

00000000; 800Е070024 or b,[00007],024

00000005; FA cli

00000006: ЗЗСО xor ax,ax

00000008; FB sti

Он изменит строку 0х6 на xor sp,sp. Это "завесит" многие отладчики и, кроме того, не позволит дизассемблерам отслеживать локальные переменные адресуе­мые через SP. Хотя IDA позволяет скорректировать стек вручную, но для этого сперва нужно понять, что sp обнулился. В приведенном примере это очевидно (хотя, кстати, и не бросается в глаза), а если это произойдет в многопоточной системе? Тогда изменение кода очень трудно будет отследить, особенно в листинге дизассемблера. Однако нужно помнить, что самомодифицирующийся код все же уходит в историю. Сегодня он встречается все реже и реже.

2-битная кодировка 3-битная кодировка
(X) ES 000 ES
01 CS 001 CS
10 SS 010 SS
11 DS 010 DS
  100 FS
  101 GS
  110 Reserved
  111 Reserved

Первоначально сегментные регистры кодировались всего двумя битами, и этого хватало, так как их было всего четыре. Позже, когда их стало больше, перешли на трехбитную кодировку. При этом два регистра llOb и Illb пока отсутствуют и вряд ли будут добавлены в ближайшем будущем. Но что же будет, если попытаться их использовать? Генерация int 0х6. А вот отладчики — эмуляторы могут вести себя странно. Иные при этом не генерируют прерывания, чем себя и выдают, а другие часто ведут себя непредсказуемо, так как требуемый регистр может находиться в области памяти, занятой другой переменной (это происходит, когда ячейка памяти определяется по индексу регистра, при этом считываются три бита и суммируются с базой, но никак не проверяются пределы).

Поведение дизассемблеров так же разнообразно. Вот, например:

HIEW:

00000000: 8Е ???

00000001: F8 с1с

00000002: СЗ retn

QVIEM:

00000000: 8EF8 mov !s,ax

00000002: СЗ ret

 

IDA:

seg000:0100 start db 8Eh

seg000:0101 db 0F8h

seg000:0102 db 0C3h

Кстати, IDA вообще отказывается анализировать весь последующий код. Как это можно использовать? Да очень просто — если эмулировать еще два сегмент­ных регистра в обработчике int 0х6, то очень трудна будет как отладка, так и дизассемблирование. Однако это опять-таки не работает под Win32!

Управляющие/отладочные регистры кодируются нижеследующим образом:

  Control Register Debug Register
CRO DRO
Reserved DRI
CR2 DR2

 

  Control Register Debug Register
CR3 DR3
CR4 Reserved
Reserved Reserved
Reserved DR6
Reserved DR7

Заметим, что опкоды операций mov, манипулирующих с ними, различны, поэтому-то и получается кажущееся совпадение имен.

С управляющими регистрами связана одна любопытная мелочь. Регистр CRI, как известно, в настоящее время зарезервирован и не используется. Так во всяком случае написано в русскоязычной документации. На самом же деле регистр CRI просто не существует! И любая попытка обращения к нему вызывает генерацию исключения int 0х6. Например, cup386 в режиме эмуляции процессора этого не учитывает и неверно исполняет программу. А все дизассемблеры, за исключением IDA, неправильно дизассемблируют этот несуществующий регистр:

 

IDA


seg000:0100 start db 0Fh

seg000:0101 db 20h

seg000:0102 db 0C8h

seg000;0103 db 0C3h

SOURCER:

43C5:0100 start

4305:0100 OF 20 С8 mov eax,Cr1

43C5;0103 C3 retn

Или:

43C5:0100 start

4305:0100 OF 20 F8 mov eax,Cr7

43C5;0103 C3 retn

Всех этих команд на самом деле не существует, и они приводят к вызову прерывания int 0х6. Не так очевидно, правда? И еще менее очевидно, что при обращении к регистрам DR4-DR5 исключения не генерируется. Между прочим, IDA 3.84 не дизассемблирует ни один регистр. Зато великолепно ассемблирует все (кстати, ассемблер был добавлен другим разработчиком).

Пользуясь случаем, акцентируем внимание на сложностях, которые подстере­гают при написании собственного ассемблера (дизассемблера). Документация Intel местами все же недостаточно ясна (как в приведенном примере), и неакку­ратность в обращении с ней приводит к ошибкам, которыми может воспользовать­ся разработчик защиты против хакеров.

Теперь перейдем к описанию режимов адресации микропроцессоров Intel. Тема очень интересная и познавательная — не только для оптимизации кода, но и для борьбы с отладчиками.

Первым ключевым элементом является байт modR/M.

| | | | |

mod reg r/m

Если mod == I lb, то два следующие поля будут представлять собой регистры. (Это так называемая регистровая адресация.

Например: оооооооо: ззсз -----------àxor ax bx

 

00000000:32СЗ-àxor аl bl

 

Как отмечалось выше, по байту modeR/M нельзя точно установить регистры. В зависимости от кода операции и префиксов размера операндов, результат может варьироваться в ту или иную сторону.

Биты 3-5 могут вместо регистра представлять уточняющий опкод (в случае если один из операндов представлен непосредственным значением). Младшие три бита всегда либо регистр, либо способ адресации. Последнее зависит от значения 'mod*. Отметим, что биты 3-5 никак не зависят от выбранного режима адресации и всегда задают либо регистр, либо непосредственный операнд.

Формат поля R/M, строго говоря, не документирован, однако достаточно очевиден. Во всяком случае понимание этого позволяет избежать утомительного запоминания совершенно нелогичной на первый взгляд таблицы адресаций (см. ниже).

 

 

R/M

3 2 1

           
     
 

 


0 — нет базирования если 2bit=0 то 0 –SI, 1 - DI

1 — есть базирование если 2bit=1 то 0 –BP, 1 - BX

если 3bit=0 то 0 –база BX, 1 – база BP

если 3bit=1 то 0 –индексный регистр 1 - базовый

Возможно, кому-то эта схема покажется витиеватой и трудной для запомина­ния, но зубрить все режимы без малейшего понятия о механизме их взаимодей­ствия еще труднее; кроме того, нет возможности себя проверить и проконтроли­ровать ошибки.

Действительно, в поле R/M все три бита тесно взаимосвязаны, в отличие от поля mod. Оно фактически задает длину следующего элемента в байтах.

Например:

[Reg+Reg]

|Опкод | |00| reg | Mem |

7 0 7 0

 

[Reg+Reg+Offset8]

|Опкод | |01| reg | Mem | |Offset 8 |

7 0 7 0 7 0

 

[Reg+Reg+Offset16]

|Опкод | |10| reg | Mem | |Offset 16 |

7 0 7 0 7 0

 

Разумеется, не может быть смещения 'offset 14', (так как процессор не оперирует с полуторными словами) и комбинация 11' указывает на регистровую адресацию.

Может возникнуть вопрос: как складывать с 16-битным регистром 8 битное смещение? Разумеется, непосредственному сложению мешает несовместимость типов, поэтому процессор сначала расширяет 8 бит до слова с учетом знака. Таким образом, диапазон возможных значений младшего байта от -127 до 127 (или от -Ox7F до Ox7F).

Все вышесказанное проиллюстрировано в таблице, расположенной ниже. Обратим внимание на любопытный момент: адресация [ВР] отсутствует. Ближай­шим эквивалентом этого является [ВР+О]. Отсюда следует, что для экономии следует избегать непосредственного использования ВР в качестве индексного регистра. ВР может быть только базой. И хотя mov ax.lbp] и воспринимается любым ассемблером, но ассемблируется в mov ах,[Ьр+0], что на байт длиннее.

Исследовав приведенную ниже таблицу, можно прийти к выводу, что все виды адресации процессора 8086 были несколько неудобны. Сильнее всего сказывалось ограничение, позволяющее использовать в качестве индекса только три регистра (BX.SI.DI). Между тем гораздо чаще требовалось использовать для этого СХ (например, в цикле) и АХ (как возвратное значение функции).

Поэтому начиная с процессора 80386 (для 32-разрядного режима) концепция адресаций была пересмотрена. Поле R/M стало всегда выражать регистр, неза­висимо от способа его использования. Последним же управляло поле 'mod', задающее (кроме регистровой) три вида адресации:

 

mod Адрес
[Reg]
(Reg+08]
[Reg+32]
Reg

Видно, что поле mod по-прежнему выражает длину следующего поля — смеще­ния, разве что с учетом 32-разрядного режима, где все слова расштряются до 32 бит.Напомним, что с помощью префикса 0х67 можно и в 16-разрядном режиме использовать 32-разрядные режимы адресации, и наоборот. Однако при stom мы сталкиваемся с интересным моментом. Разрядность индексных регистров остается 32-битной и в 16-разрядном режиме!

В реальном режиме, где нет понятия границ сегментов, это действительно будет работать так, как выглядит, и мы сможем адресовать первые 4 гигабайта памяти (32 бита), что позволит преодолеть печально известное 64-килобайтовое ограничение 8086 процессоров. Но такие приложения окажутся нежизнеспособ­ными в защищенном или V86 режиме. Попытка вылезти за границу 64-килобай-тового сегмента вызовет исключение OxD, что приведет к автоматическому закрытию приложения, — скажем, под управлением Windows. Аналогично посту­пают и отладчики (в том числе и многие эмуляторы, включая сир386).

Сегодня актуальность этого приема, конечно, значительно снизилась, посколь­ку "голый DOS" практически уже не встречается, а режим его эмуляции Windows'oM крайне неудобен для пользователей.

16-режим
Адрес Mod K/M
ibx+sii
[BX+DI]
IBP+SI]
[BP+Dll
[SI]
idi]
смещ16
ibx]
[ВХ+8Ц+смещ8
[BX+Dll+смещВ
1ВР+311+смещ8
[ВР+ОЦ+смещВ
[ЗЦ+смещ8
lDII+смещв
[ВР1+смещ8
[ВХ1+смещ8
[ВХ+31]+смещ16
1ВХ+01]+смещ16
[ВР+31]+смещ16
1ВР+01]+смещ16
[81]+смсщ16
[DI]+CMeiul6
1ВР]+смещ16
1ВХ]+смещ16

 

 

32-режим
Адрес Mod R/M
[EAX]
[ЕСХ]
[EDX]
[ЕВХ]
[-11"]
сме1д32
[Esll
[EDI]
смещ81ЕАХ]
смещ8[ЕСХ1
смещ81ЕОХ1
смещ8[ЕВХ1
смещ81"]1-1
смещ8[еЬр]
cMeia8lESII
смещ81ЕВ1]
смещ32[ЕАХ]
смещ321ЕСХ1
смещ32[ЕОХ]
смещ32[ЕВХ]
смещ321-]1"1
смещ8[еЬр]
смещ8[Е31]
смещ8[Е01]

 

.. Изучив эту таблицу, можно решить, что система адресации 32-разрядного режима крайне скудная и ни на что серьезное ее не хватит. Однако это не так. В Э&6+ появился новый байт SIB (Scale-Index Base).

Процессор будет ждать его вслед за R/M всякий раз, когда последний равен lOOb. Эти поля отмечены в таблице как '{-]'. SIB хорошо задокументирован, и назначения его полей показаны на рисунке ниже. Нет совершенно никакой нужды зазубривать таблицу адресаций.

| Scale | Index | Base |

7 6 3 0

‘Base' — это базовый регистр. Index — индексный, а два байта Scale — это степень двойки для масштабирования.

Поясним введенные термины. Что такое индексный регистр, понятно всем. Например, [SI]. Теперь же можно выбирать любой регистр в качестве индексного. Правда, за исключением SP (впрочем, можно выбирать и его, но об этом позже),

Базовый регистр — это тот, который суммировался с индексным, например, [BP+SI]. Точно так же теперь можно выбрать любой регистр в качестве базового. При этом есть возможность выбрать SP. Заметим, что если мы выберем последний в качестве индексного, то получим вместо 'SP' — "никакой". В этом случае адресацией будет управлять только базовый регистр.

Наконец, масштабирование — это уникальная возможность умножать индек­сный регистр на 1,2,4,8 (т.е. степень двойки, которая задается в поле Scale). Э.то очень удобно для доступа к различным структурам данных. При этом индексный регистр, являющийся одновременно и счетчиком цикла, будет указы­вать на следующий элемент структуры даже при единичном шаге цикла, что чаще всего и встречается.

Если при этом в качестве базового индекса будет выбран ВР, то полученный режим адресации будет зависеть от поля MOD предыдущего байта. Возможны следующие варианты:

mod Действие
Смещение32[indех]
Смещение8 [EBP] [index]
Смещение32[ЕВР] [index]

Итак, мы полностью разобрались с кодировкой команд. Осталось лишь вычис­лить непосредственно саму таблицу опкодов, и можно отправляться в длинный и тернистый путь написания собственного дизассемблера.

Base EAX ECX EDX EBХ ESP [*] ESI EDI
Index S Шестнадцатиричные значения SIB  
lEAX]  
lECX]  
IEDXI   OA IA 2A ЗА
[EBX]  
Отсутствует ОС IC 2C ЗС
lEBP]   OD ID 2D 3D
IESI]   OE IE 2E ЗЕ
lEDI]   OF IF 2F 3F
[EAX*2]  
IECX*2l  
[EDX»2]   4A 5A 6A 7A
lEBX*2l  
Отсутствует 4C 5C 6C
lEBP*2]   4D 5D 6D 7D
[ESI^]   4E 5E 6E 7E
lEDI»2]   4F 5F 6F 7F
lEAX^]   АО A8 ВО B8
(ECX•4]   А1 A9 В1 B9
lEDX^I   8A 9A A2 AA B2 BA
lEBX«4]   A3 AB B3 BB
Отсутствует 8C 9C A4 AC B4 ВС
[EBP^I   8D 9D A5 AD B5 BD
lESIM)   8E 9E A6 AE B6 BE
[EDI'4]   8F 9F A7 AF BF
lEAX-el   CO C8 DO D8 EO E8 FO F8
lECX*8]   С1 C9 DI D9 El E9 FI F9
[EDX*8]   C2 CA D2 DA E2 EA F2 FA
[EBX*8l   C3 CB D3 DB ЕЗ EB F3 FB
Отсутствует C4 CC D4 DC Е4 EC F4 FC
[EBP«8l   C5 CD D5 DD E5 ED F5 FD
[ESI»8l   C6 CE D6 DE E6 ЕЕ F6 FE
lEDI»8]   C7 CF D7 DF E7 EF F7 FF

 

За это время, надеюсь, у вас разовьются достаточные навыки для ассембли-рования/дизассемблирования в уме. Впрочем, есть множество эффективных приемов, позволяющих облегчить сей труд. Ниже я покажу некоторые из них. Попробуем без дизассемблера взломать crackmeOI. corn. Для этого даже не обязательно помнить опкоды всех команд!

Итак, для начала поищем, что выводит текст 'Crack me..Type password'. В самом файле начало текста расположено со смещением 0х77. Следовательно, если учесть, что corn-файлы загружаются начиная со смещения 0х100, эффективное смещение равняется 0х100+0х77==0х177. Учитывая обратное расположение старших и младших байт, ищем в файле последовательность 0х77 0х01.

00000000: В4 ОЭ БД 77 01 CD 21

Вот она! Но что представляет собой опкод ОхВА? Попробуем определить это in трем младшим битам. Они принадлежат регистру DL(DX). А ОхВ4 0х9 — это шоу АН,9. Теперь нетрудно догадаться, что оригинальный код выглядел следую­щим образом:

Mov ah,9

Mov dx, 0x177

И это при том, что не требуется помнить опкод команды MOV! (Хотя это очень распространенная команда, и ее опкод запомнить все же не помешает.)

Вызов 21-го прерывания OxCD 0х21 легко отыскать, если запомнить его символьное представление '=" в правом окне дампа. Как нетрудно видеть, следующий вызов int 21 лежит чуть правее по адресу ОхС. При этом DX указывает на 0х156. Это соответствует смещению 0х56 в файле. Наверняка эта функция читает пароль. Что ж, уже теплее. Остается выяснить, кто и как к нему обращается. Ждать придется недолго.

При разборе байта ОхЕ не забудьте, что адресации [ВР) не существует в природе. Вместо этого мы получим [offsetl6l. На размер регистра и приемник результата указывают два младших бита байта Ох8А. Они равны 10Ь. Следова­тельно, мы имеем дело с регистром CL, в который записывается содержимое ячейки [0х156].

Все, кто знаком с ассемблером, усмотрят в этом действии загрузку длины пароля (первый байт строки) в счетчик. Неплохо для начала? Мы уже дизассем-блировали часть файла и при этом нам не потребовалось знание ни одного опкода операции, за исключением, быть может OxCD =^ INT. Продолжим в том же духе.

Вряд ли мы скажем, о чем говорит опкод 0х87. (Впрочем, обращая внимание на его близость к операции NOP = xchg ах,ах, можно догадаться, что 0х87 — это опкод операции xchg). Обратим внимание на связанный с ним байт OxF2:

F2 — 11110010

|---|---|-----|

Reg/Rag •—| | |—> (DX) (SI)

(SI)

Как нетрудно догадаться, эта команда заносит в SI смещение пароля, содержащееся в DX. Этот вывод мы делаем только исходя из смыслового значения регистров, полностью игнорируя опкод команды. К сожалению, этого нельзя сказать о следующем байте — ОхАС. Это опкод операции LODSB, и его просто придется запомнить.

0х02 — это опкод ADD, а следующий за ним байт — это AH.AL (не буду больше повторяться).

ОхЕ2 — это опкод операции LOOP, а следующий за ним байт — это знаковое относительное смещение перехода.

00000010: 56 01 87 F2 AC 02 EO E2 FB BE 3В 01 30 24 46 61

Чтобы превратить его в знаковое целое, необходимо дополнить его до нуля (операция NEG, которую большинство калькуляторов не поддерживают). Тот же результат мы получим, если отнимем от 0х100 указанное значение (если разговор идет о байте). В нашем примере оно равно пяти. Отсчитаем пять байт влево от началаследующей команды. Если все сделать правильно, то вычисленный переход должен указывать на байт ОхАС == LODSB, впрочем, последнее было ясно и без вычислений, ибо других вариантов, по-видимому, не существует.

Почему? Да просто данная процедура подсчета контрольной суммы (ил» точнее, хеш-суммы) очень типична. Конечно, не стоит всегда полагаться на свой интуицию и "угадывать" код, хотя это все же сильно ускоряет анализ.

С другой стороны, хакер без интуиции — это не хакер. Давайте применим нашу интуицию и "вычислим", что представляет опкод следующей команды. Вспомним, что ОхВ4 (== 10110100) это MOV AH,imm8.

ОхВЕ очень близко к этому значению, следовательно, это операция MOV. Осталось определить регистр-приемник. Рассмотрим обе команды в двоичном виде:

Mov ah, imm8 mov ??, ???

10110100 10111110

--------|----- ------|------

mov'__I | I___.AH(SP) •mov'•—1 | IDH(SI)

|--------------size-------------------|

Как уже говорилось выше, младшие три бита — это код регистра. Однако его невозможно однозначно определить без уточнения размера операнда. Обратим внимание на третий (считая от нуля) бит. Он равен нулю для АН и единице в нашем случае. Рискнем предположить, что это и есть бит размера операнда: хотя этого явно и не утверждается Intel, но вытекает из самой архитектуры команд и устройства декодера микропроцессора.

Заметим, что это, строго говоря, частный случай, — могло бы оказаться и иначе. Так, например, четвертый справа бит по аналогии должен быть флагом направления или знакового расширения, но, увы, — таковым в данном случае не является. Четыре левые бита — это код операции 'mov reg.nnru'. Запомнить его легко — это "13" в восьмеричном представлении.

Итак, ОхВЕ 0х38 0х01 — это MOV SI,Oxl3B. Скорее всего Ох13В — это смещение, и за этой командой последует расшифровщик очередного фрагмента кода. А может быть и нет — это действительно смелое предположение. Однако 0х30 0х24 это подтверждают. Хакеры обычно настолько часто сталкиваются с функций хог, что чисто механически запоминают ее опкод.

Нетрудно установить, что эта последовательность дизассемблируется как XOR [SI],AH. Следующий байт 0х46 уже нетрудно "угадать" — INC SI. Кстати, рассмотрим, что же интересного в этом опкоде:

--|--

|-AHSI

Третий бит равен нулю! Выходит, команда должна выглядеть как INC АН! (Что, кстати, выглядит непротиворечиво в смысле дешифровщика.) Однако все же это INC si. Почему мы решили, что третий бит — флаг размера? Ведь Intel этого никак не гарантировала! И INC byte вообще выражается через дополнитель-вый код, что на байт длиннее.

Таким образом, как ни полезно знать архитектуру инструкций, все же таблицу опкодов хотя бы местами надо просто выучить. Иначе можно впасть в глубокие и грубые ошибки. Хотя, с другой стороны, знание архитектуры очень и очень помогает.

81| V.3.M.pT.=J;.

05FB 00000020;FE 56 01 72 F7 IE 02 ОС 81 FE 3В 01 73 F7 BO F9 | .V.IfN..E.!.a.R.

Но продолжим наш анализ. Байт 0х81 это, если можно так выразиться, "гейт". Наконецто мы с ним столкнулись. Он открывает доступ к целому семейству команд, манипулирующих регистром-непосредственным значением. "Гейтов" не так много, и всех их нужно знать наизусть. Они только указывают на группу команд, а конкретный код задается в поле reg следующего байта

Таким образом, достаточно помнить, что Illb — обозначают СМР. Хотя об этом косвенно свидетельтствует и сам операнд. Взгляните:

OOOQ0020:FE 56 01 72 F7 4Е 02 ОС 81 FE 3В 01 73 F7 ВО F9 I .V.ryH..E.;.S.A.

Мы должны помнить, что это смещение нам уже встречалось в качестве буфера для вводимого пароля. Можно предположить, что расшифровщик проверяет границы и прекращает шифровку в этом месте. Ясно видно, что зашифрованный блок лежит между расшифровщиком и самим паролем. И тогда команда сппр SI, offset psw логична и уместна.

Кому-то описываемый подход может показаться ненаучным, а то и полумисти-ческим. Но это не так. Надо просто почаще ставить себя на место разработчика и думать: "а как бы я написал этот код?". В большинстве случаев действие потенциального противника можно предугадать.

00000020:FE 56 01 72 F1 4E 02 ОС 81 FE 3В 01 73 F7 ВО F9 I .V.r»K..B.;,s.a.

Команды из серии 7х известны, наверное, каждому, кто хотя бы раз "правил байтики" в чужой программе. Конечно же, это условный переход! А как узнать само условие? Для этого необходимо вспомнить рассмотреть эту команду более детально

Само условие (упакованный регистр флагов) есть проверка флага переноса или, в более понятной мнемонике, JB. Самый правый бит — логическое 'NOT равен нулю; следовательно, это "прямое" условие, т.е. JB ххх так и есть. Адрес перехода OxF7 можно угадать не вычисляя. Но на всякий случай проверим:

00000010:56 01 87 F2 ACj02 EO E2 FB BE 3В 01**30 24 46 81 I V.3.M.pT.^;.OFB 00000020:FE 56 01 72 F^I-^E 02 ОС Bl FE 3В 01 73 F7 80 F9 I .V.r^H..B.;.S.A

Оно равно -9 и переходит на операцию 'XOR'. Последующий код очень близок к 0х46 — INC SI. Особенно это хорошо заметно в двоичном представлении:

Тут мы сталкиваемся еще с одним недокументированным полем, которое характерно для некоторых команд. А именно с полем знака. Во втором случае он отрицательный. Следовательно, INC сменяется на DEC. Конечно, не зная самой команды, нельзя предугадать значение третьего бита, но тут есть одна хитрость. Дело в том, что однотипные команды объединены в группы, в которых действуют свои локальные условия. Отбросив три бита, мы получим, что 0х46 и Ох4Е отличаются друг от друга всего на единицу, а значит "территориально" очень близки.

То же можно сказать и про опкод 0х2. Наверно, большинство знает, что 0х0 0х0 0х0... это сложение чего-то с чем-то, не так ли? Теперь можно установить, чю 02 или 10 в двоичном представлении — это сложение одного операнда размером в байт с другим. Что это за операнды — покажет следующее поле ОхС:

Таким образом, получается CL -> (SIJ. Но, вспоминая обратный бит направ­ления в предыдущем байте, меняем операнды местами. И у нас получается ADD CL,[SU. Похоже, считается контрольная сумма расшифрованного фрагмен­та. Очевидно, что следующей командой будет СМР SI,Oxl3B.

Действительно, это смещение наблюдается в дампе, равно как и 0х81. Следовательно, OxFE в таком случае будет cmp si,offset 16, И это вряд ли нужно • проверять.

А теперь обратим внимание на следующий код (0х73 OxF? по понятным причинам мы опускаем): ,

80 F9 I .V.r^H..E.;.s.A.

Нам он уже встречался. Нетрудно вспомнить, что это cmp cl,imm8. Само «епосредственное значение можно найти в следующем байте:

00000030:03 74 08 В4 09 BA BE 01 CD 21 СЗ ВО 94 29 9Д Q4 I l-t.^.ll=J.*lf-:^)bd

Оно равно ОхСЗ. Теперь следующая команда передает управление расшифро­ванному коду. Чтобы это действительно произошло, необходимо ввести правиль­ный пароль. Как его найти, было подробно рассказано выше; кроме того, этот вример был разобран "по косточкам" и успешно взломан. Поэтому не будем иа этом останавливаться.

Но, чтобы найти пароль, нужно написать переборщик, а в нашем распоряже-; нии, как мы условились, ни одного компилятора нет. И тут мы подходим к 1 следующему этапу: как писать программы непосредственно "с нуля", без единого 1 инструмента под рукой...

– Конец работы –

Эта тема принадлежит разделу:

NAG SCREEN

На сайте allrefs.net читайте: "NAG SCREEN"

Если Вам нужно дополнительный материал на эту тему, или Вы не нашли то, что искали, рекомендуем воспользоваться поиском по нашей базе работ: Структура команд INTEL 80х86

Что будем делать с полученным материалом:

Если этот материал оказался полезным ля Вас, Вы можете сохранить его на свою страничку в социальных сетях:

Все темы данного раздела:

NAG SCREEN
Вероятно, до разработчиков защит наконец-то дошел тот малоприятный факт, что на языке высокого уровня чрезвычайно трудно создать что-нибудь устойчивое даже против кракера средней квалификации. Наве

SetTimer
18C Is Iconic 195 KillTimer B7 EnableWindow 146 GetSystemMetrics 19E LoadIconA Попробуем найти код, который вызывает SetTimer, для чего установим на пос

Ограничение возможностей
Многие незарегистрированные версии отличаются тем, что часть их возмож­ностей заблокирована. Если программа предусматривает регистрацию, то обычно больших проблем при взломе не возникает. Совсем др

Text;004015CFp
jmp ds: ?EnableWindow@cwnd@@QREHH@z j_?EnableWindow@cwnd@@QAEHH@z endp Их всего два. Как раз по числу элементов управления. Пока защита не предвещает ничего необычного и ее код вы

Ключевой файл
Настал черед рассмотреть и ключевые файлы. Обычно это самая сложная защита из всех вышеизложенных^ поскольку может сочетать в себе как мощную шифровку, так и недостающие фрагменты кода. Но именно

Способы затруднения анализа программ
Все методы взлома защитных механизмов (за исключением, возможно, крип­тографических) сводятся практически к двум моментам — локализации кода защиты в сотнях килобайт (мегабайт) кода приложения и ан

Приемы против отладчиков
Самым первым отладчиком под MS-DOS был Debug.com фирмы MicroSoft. Совершенно очевидно, что этот инструмент годился разве что для забавы и изучения ассемблера. Но рынок не терпит пустого ме

Приемы против отладчиков реального режима
Чтобы понять, как противодействовать отладчикам реального режима, необ­ходимо изучить сам процесс отладки. 8086 процессор предоставлял для этого одну команду, один флаг и две исключительные ситуаци

Приемы против отладчиков защищенного режима
Позже появился 80286 (с точки зрения хакера мало чем отличавшийся от своего предшественника), а вслед за ним и 80386, принесший принципиально новые возможности отладки. Точнее, "принципиально

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

Дизассемблирование в уме
"— Мне известны политические аргументы. — Но меня интересуют человеческие доводы." Ф. Херберт. "Мессия Дюны". Очень часто под рукой не оказывается ни отладчика,

Маленькие хитрости
"Главная часть дисциплинирующей выучки — это ее сокрытая часть, предназначенная не освобож­дать, но ограничивать." Ф. Херберт. "Еретики Дюны". Хорошо, если в ваш

Ассемблирование в уме
"Ничто не превосходит по сложности человече­ский ум." Ф. Херберт. "Еретики Дины". Мы уже проделали титаническую работу, дизассемблировав в уме крохотный файл в п

Text 00000452 |D:KPNCHIEWDEXEM.EXE
При этом кроме собственно имен сохранятся текущий режим и позиция курсора (что особенно приятно). Последнее позволяет использовать HIEW для чтения больших текстовых файлов (электронных книг, докуме

Ассемблер
"Убийство острием лишено артистизма. Но пусть тебя это не останавливает, если плоть, раскрываясь, сама себя предлагает." Ф. Херберт. "Дюна". Пере

Дизассемблер
Дизассемблер в HIEW великая вещь. фактически это основной режим работы хакера. Не то чтобы некоторые ленились дизассемблировать в уме hex-коды, (что, скажем, частенько приходится делать при работе

Манипуляции с блоками
"Соединение невежества и знания, соединение ди­кости и культуры — не начинается ли оно с того чувства достоинства, с которым мы относимся к своей смерти?" Ф. Хербер

Поддержка LE/PE/NE/LX/NLM-ФОРМАТОB
"Понятие прогресса, служит защитным механиз­мом, отгораживающим нас от ужасов будущего." Ф. Херберт. "Дюна". Вообще-то шестнадцатиричный редактор идеологически д

Калькулятор
"Врагу, которым восхищаешься, легче вселить в тебя ужас" Ф. Херберт. "Дюна". Необходимость встроенного калькулятора сегодня сомнений ни у кого не вызывает. Хакер

Калькулятор
"Врагу, которым восхищаешься, легче вселить в тебя ужас" Ф. Херберт. "Дюна". Необходимость встроенного калькулятора сегодня сомнений ни у кого не вызывает. Хакер

Калькулятор
"Врагу, которым восхищаешься, легче вселить в тебя ужас" Ф. Херберт. "Дюна". Необходимость встроенного калькулятора сегодня сомнений ни у кого не вызывает. Хакер

Крипт-система
"Не считай человека мертвым, пока не увидишь его тело. И даже тогда ты можешь ошибиться." Ф. Херберт. "Дюна". Уникальность HIEW-a прежде всего в том, чт

Описание файла HIEW.INI
"— Осторожность — важное качество для чело­века, который будет вождем." Ф. Херберт. "Дюна". HIEW хранит часть настроек в ini-файле, который немного напоминает од

Хотите получать на электронную почту самые свежие новости?
Education Insider Sample
Подпишитесь на Нашу рассылку
Наша политика приватности обеспечивает 100% безопасность и анонимность Ваших E-Mail
Реклама
Соответствующий теме материал
  • Похожее
  • Популярное
  • Облако тегов
  • Здесь
  • Временно
  • Пусто
Теги