Возможно ли изменить сложный код инструкции в архитектуре x86?

Например, PUSH imm32 имеет код операции 68h. Можно ли использовать другое число, например 69h, для "представления" этой инструкции (предположим, что этот номер не используется другими инструкциями)?

Под "представлением" я подразумеваю, где в сборке есть инструкция PUSH, в двоичном исполняемом файле появится 69h. Когда он в конечном итоге выбирается и выполняется процессором, он возвращается на 68ч.

Я понимаю, что каждый код операции специально разработан в соответствии со схемой ЦП, но возможно ли, что я просто хочу использовать другое шестнадцатеричное число в качестве суррогата?

Конечно, я не буду вносить никаких изменений в процессор, и все же хочу, чтобы инструкция выполнялась на архитектуре x86.

Обновление: почему я задаю этот вопрос?

Вероятно, вы знаете о Return Oriented Attack, который целенаправленно неверно истолковывает поток машинных языков и использует то преимущество, что в стандартной библиотеке много C3 (то есть ret). Сначала я думал, что если мы сможем изменить код операции возврата с C3 на какой-то другой код, предпочтительно 2 байта, то ROA не будет работать. Я не эксперт в области архитектуры, и я просто обнаружил, что моя мысль не будет работать в реальности. Спасибо за все ваши ответы.

2 ответа

Решение

Сначала я думал, что если мы сможем изменить код операции возврата с C3 на какой-то другой код, предпочтительно 2 байта, то ROA не будет работать.

Нет, кодировки команд x86 являются фиксированными и в основном аппаратными в кремнии декодеров внутри процессора. (Микрокодированные инструкции перенаправляют в ПЗУ микрокода для определения инструкции, но код операции, который распознается как инструкция, все еще жестко запрограммирован.)

Я думаю, что даже обновление микрокода от Intel или AMD не может изменить существующие процессоры, чтобы не декодировать C3 как ret, (Хотя, возможно, они могли бы сделать какую-то другую многобайтовую последовательность также декодировать как очень медленный микрокод ret, но, вероятно, только принимая кодировку для существующей микрокодированной инструкции.)


Процессор, который не декодировал C3 как ret не будет процессором x86 больше. Или, я думаю, вы могли бы сделать это новым режимом, в котором кодировки команд были другими. Впрочем, он больше не будет бинарно-совместимым с x86.

Хотя это интересная идея. Однобайтовое RET на x86 значительно упрощает объединение гаджетов ( https://en.wikipedia.org/wiki/Return-oriented_programming). (Или означает, что есть много других гаджетов, которые можно объединить в цепочку, предоставляя вам больший набор инструментов.)

Я бы не стал задерживать дыхание, ожидая, пока производители процессоров предоставят новый режим, в котором ret использует 2-байтовый код операции. Впрочем, было бы возможно (для производителей ЦП создать новый дизайн, а не для взлома существующего ЦП). Сделав его отдельным режимом (например, 64-разрядный длинный режим или 32-разрядный режим сжатия в 64-разрядном ядре или "устаревший режим" с 32-разрядным ядром) операционные системы все равно будут работать на таких процессорах, и вы может смешивать / сопоставлять процессы пользовательского пространства под тем же ядром, некоторые скомпилированы для x86, а некоторые для new86.

Если производители собираются ввести новый несовместимый режим, который не может запускать существующие двоичные файлы, мы надеемся, что они сделают другие очистки для набора инструкций. например, удаление ложной зависимости от FLAGS для сдвигов с переменным счетом, заставляя их всегда записывать FLAGS, даже если count = 0. Или полностью переделывать коды операций, чтобы не тратить так много места для кодирования на 1 байт xchg eax, r32 и сократить кодировки для SIMD-инструкций. Но тогда они не могли использовать столько же транзисторов декодера, сколько обычные декодеры x86. И любые изменения, такие как семантика EFLAGS для смен, могут потребовать изменений в бэкэнде, а не только в декодерах.

Они могли бы также сделать [rsp+disp8/32] режимы адресации на 1 байт короче, возможно, используется другой регистр, как тот, который всегда нуждается в бите SIB, даже без индекса. (-fomit-frame-pointer типично сейчас, так что это отстой, что адресация относительно указателя стека стоит лишний байт.)

См. Статью Агнера Фога " Остановите набор команд" в блоге о войне для получения более подробной информации о том, какова кодировка команд mess x86.


Сколько изменений в схеме процессора потребуется как минимум, чтобы сделать c3 начало 2-байтовой инструкции, которая требовала, чтобы 2-й байт был 00 ?

Процессоры Intel декодируют в несколько этапов:

  • Предварительный декодер длины команд находит границы команд, помещая байты команд в очередь (обработка до 16 байтов или 6 команд, в зависимости от того, что меньше, за цикл). См. https://www.realworldtech.com/sandy-bridge/3/ для блок-схемы.

  • Декодеры извлекают 4 (или 5 в Skylake) инструкции из этой очереди и подают их параллельно фактическим декодерам. Каждый выводит 1 или более мопов. (См. Следующую страницу в обзоре Дэвида Кантера SnB).

Некоторые процессоры отмечают границы команд в кэше L1i и выполняют это декодирование, когда строка поступает из L2. (AMD сделала это совсем недавно, чем Intel, но IIRC Ryzen этого не делает, а Intel нет в семействе P6 или SnB. См . Руководство по микроарху Agner Fog.)

Дело в том, что c3 Это однобайтовый код операции без последующих байтов, встроенный в декодеры длины команд, поэтому его придется изменить.

Но тогда как обрабатывать 2-й байт? Вы можете иметь декодер, который получает c3 xx Проверь это xx == 00 и поднять #UD исключение, если нет (UnDefined инструкция, иначе недопустимая инструкция).

Или это может быть декодировано как imm8 операнд, и имейте исполнительный блок, проверяющий, что операнд был 0.

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


00 не "особенный". Обычные декодеры, вероятно, получают байты инструкций в широком входе, длина которого, вероятно, составляет 15 байтов (максимальная длина инструкций x86). Но нет никаких оснований предполагать, что они будут рассматривать биты / байты после длины инструкции и ошибки, если она не была расширена нулями. Он может быть спроектирован таким образом, но с такой же вероятностью вручаются однобайтовые коды операций, такие как c3 является аппаратным средством и не имеет старших битов ANDed, ORed или XORed с любым из битов кода операции.

Код операции или целый insn не является целым числом, которое должно быть расширено до нуля. Вы не можете предполагать, что есть что-то вроде "регистра команд".


Изготовление c3 xx не декодировать как ret поскольку xx!=0 все равно сломает практически все существующие двоичные файлы и все равно потребует нового режима, если вы создаете процессор, который может работать таким образом.

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

Также c3 в последнем байте страницы, за которым следует не отображенная страница, не должно быть ошибки страницы. Но это произошло бы, если бы этап декодирования длины инструкции всегда выбирал другой байт после c3 прежде чем дать ему расшифровать. (Запуск кода из не кэшируемой памяти также сделал бы это число заметным изменением. UC - это эквивалент процессора volatile)

Я полагаю, вы могли бы подделать этап декодирования длины на подделку 00 байт для декодеров, если работает в режиме, где ret это один байт. ret это безусловный прыжок, но он может ошибиться, если [rsp] не читается Но я думаю, что у рамки исключения будет просто начальный адрес инструкции, а не длина. Таким образом, для остальной части конвейера могло бы быть хорошо думать, что это была 2-байтовая инструкция, когда на самом деле она была только 1.

Но он все равно должен как-то помещаться в uop-кеш, а кеш uop должен заботиться о начальных / конечных адресах insn даже для безусловных переходов. Для инструкции, которая охватывает 64-байтовую границу строки кэша, она должна была бы аннулировать инструкцию, если любая из них изменена.

Насколько я понимаю, реальная разработка ЦП всегда сложнее и сложнее, чем вы можете себе представить, рассматривая блок-схемы, подобные статьям Дэвида Кантера.


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

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

Было бы намного проще изменить процессорный процессор, который всегда требует c3 следовать 00, но тогда это был бы не x86, и он не мог бы выполнять подавляющее большинство кода. У Intel или AMD практически нет шансов продать такой процессор.

В теории да...

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

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

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

Я не должен упоминать, что это громоздкий AF. Это было бы довольно просто в DOS, поскольку для современных ОС это решение практически не используется.

Другие вопросы по тегам