четверг, 10 ноября 2022 г.

Почему x86-64 функции Windows НЕ начинаются с бессмысленной инструкции MOV EDI, EDI?

Это перевод Why don’t Windows functions begin with a pointless MOV EDI,EDI instruction on x86-64? Автор: Реймонд Чен.

Когда-то мы обсуждали, почему все функции Windows начинаются с бессмысленной инструкции MOV EDI, EDI. Ответ заключался в том, что эта инструкция использовалась как двухбайтовый NOP, что позволяло безопасно заменить её на инструкцию перехода JMP $-5, что позволяло применять различные исправления к работающей системе (такой метод не подходит для тех исправлений, которые изменяют структуры данных или включают взаимодействие между процессами).

Но вы могли заметить, что в 64-битной Windows эти бессмысленные инструкции исчезли. Значит ли это, что метод мёртв?

Нет, этот способ всё ещё применяется. Но в 64-битной Windows точка для внедрения реализована иначе.

Идея состоит в том, что нам не нужно вставлять в каждую функцию бессмысленную двухбайтовую инструкцию NOP, если первая (настоящая) инструкция функции уже является двухбайтовой инструкцией (или длиннее). В этом случае эта инструкция сама по себе может служить точкой для внедрения.

Случай, когда первая инструкция функции занимает два байта или больше, является наиболее распространенным. В x86-64 осталось всего несколько однобайтовых инструкций. Вот те, с которыми вы можете столкнуться в коде, сгенерированном компилятором пользовательского режима:
PUSH R LEAVE CWDE INT 3
POP R RET CDQ NOP
где R — 64-битная версия одного из восьми именованных (не пронумерованных) регистров.

Некоторые из этих инструкций не могут стоять в начале функции:
  • LEAVE не имеет смысла, потому что она изменяет регистр, который мы (как вызываемый) должны сохранять.
  • CWDE и CDQ не имеют смысла, потому что они используют RAX в качестве входного регистра, но этот регистр не определен при входе в функцию.
  • NOP можно просто опустить.
  • Начало функции с POP запрещено Win32 ABI: адрес возврата должен оставаться в стеке.
Остаётся всего пара инструкций, которые можно обойти:
  • PUSH: если функция вносит в стек какие-либо регистры R8 или выше, компилятор может поставить эти инструкции в начало функции, поскольку внос в стек регистра с верхним номером является двухбайтовой инструкцией. Либо же компилятор может перекодировать инструкцию с избыточным префиксом REX 0x48. Наконец, в качестве альтернативы компилятор может сохранить регистр в домашнем пространстве (home space), для чего используется многобайтовая инструкция MOV [RSP+n], R.
  • RET: это происходит, если функция пуста и не возвращает значения. Тогда компилятор может изменить её на 3-байтовый RET 0 или 2-байтовый REPZ RET.
Последняя оставшаяся инструкция — INT 3 (программная точка останова).

Один из вариантов — использовать альтернативную двухбайтовую кодировку CD 03 (INT nn, где nn = 3). Однако код, который установил INT 3, может полагаться на то, что это именно однобайтовая инструкция, потому что он намеревается исправить её с помощью однобайтового NOP, или он намеревается обработать исключение "точка останова", перешагнув код инструкции путём увеличения указателя инструкции на 1.

Вместо этого компилятор перестраховывается и начинает функцию с двухбайтового NOP, который закодирован как XCHG AX, AX - и, действительно, отладчик Microsoft именно так его и дизассемблирует.

Бессмысленная инструкция MOV EDI, EDI исчезла. И в большинстве случаев компилятор может жонглировать командами так, что вы даже не заметите, что он устроил так, чтобы первая инструкция вашей функции была многобайтовой инструкцией. Единственный случай, когда компилятор терпит неудачу, это если первой инструкцией вашей функции является INT 3, но тогда компилятор вставляет бессмысленную инструкцию XCHG AX, AX, также известную как двухбайтная команда NOP.

Комментариев нет:

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

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

Примечание. Отправлять комментарии могут только участники этого блога.