Самомодифицирующийся код

Материал из Википедии — свободной энциклопедии
Перейти к: навигация, поиск

Самомодифицирующийся код (СМК) — программный приём, при котором приложение создаёт или изменяет часть своего программного кода во время выполнения. Такой код обычно применяют в программах, написанных под процессор с фон-неймановской организацией памяти.

По времени проведения модификации метод делится на

  • Модификация при инициализации — проводится один раз, перед запуском изменяемого кода
  • Модификация на лету (on-the-fly) — изменение состояния программы во время исполнения

В обоих случаях изменение проходит непосредственно в машинном коде, когда новые инструкции перезаписывают старые (напр. условный переход JZ, JNZ, JE, JNE и т.п. заменяются на безусловный переход JMP или NOP). В наборе инструкций IBM/360 и Z/Architecture имеется инструкция EXECUTE (EX), которая перезаписывает целевую инструкцию (записанную во втором байте команды EX) самыми младшими 8 битами регистра 1. На указанных архитектурах с её помощью реализуется стандартный, законный метод временного изменения инструкций.

Назначение[править | править вики-текст]

Основные применения самомодифицирующегося кода:

  • В критичных к безопасности местах для усложнения исследования кода (полиморфные вирусы, некоторые типы защиты от копирования, упаковщики и т. д.).
  • В критичных к скорости местах для ускорения работы. Так, например, во время исполнения можно уменьшить длину критического пути исполнения. Вместо установки и последующей многократной проверкой флагов с условными переходами, можно всего лишь изменить адрес и тип перехода в машинном коде. Многие порты движка Doom устанавливали прямо в машинном коде ширину экрана, это ускоряло отрисовку столбца[1].
  • Иногда используется для включения/отключения во время исполнения некоторой функциональности для тестирования или отладки. Так, в ОС Linux и Solaris при использовании отладочных инструментов Kprobes и DTrace в некоторые места кода ядра или программ вставляются последовательности инструкций nop. При включении инструмента некоторые из этих последовательностей заменяются на безусловный переход на процедуру отладки. Использование СМК позволяет расставить значительное количество точек, в которых возможна отладка, слабо при этом влияя на скорость исполнения с отключенной отладкой.
  • В ядре Linux и, возможно, других ОС, используются для отключения частей ядра, ненужных в данном окружении. При загрузке Linux определяет, исполняется ли он на SMP или на однопроцессорной машине. Во втором случае часть примитивов синхронизации удаляется из кода ядра.

Применимость к процессорам с Гарвардской архитектурой[править | править вики-текст]

В Гарвардской архитектуре память для кода и память для данных разделены. Соответственно, в них сильно усложняется работа самомодифицирующегося кода. Хотя архитектура x86 определена как фон-неймановская (с единой памятью кода и данных), большинство современных процессоров имеют раздельные области кэша для кода и для данных. При этом кэш кода не поддерживает запись, и при изменении закэшированного участка памяти может потребоваться либо аппаратно проведенный частичный или полный сброс кэша кода (x86) либо явная инструкция процессору на сброс кэша кода (sparc). Из-за этого только что измененный код может исполняться медленнее, либо потребовать дополнительных команд для правильной работы. Также изменение кода сбрасывает конвейер процессора.[2]

Также, некоторые идеи Гарвардской архитектуры реализуются в ОС (например, Data Execution Prevention в ОС Windows, W^X в OpenBSD) и в процессорах (для x86 — бит NX и подобные). В этих реализациях отдельные фрагменты памяти могут быть помечены как неисполняемые (то есть данные) или как исполняемые но немодифицируемые (то есть код без права на изменение). Использование самомодифицирующегося кода в таких программных окружениях усложняется, так как его приходится располагать либо в незащищенной области памяти (иногда такой областью является стэк), либо явно отключать защиту для подлежащего изменению кода.

Использование[править | править вики-текст]

  • JIT (Just in time — компиляция)
  • Динамическая трансляция
  • Динамическая рекомпиляция — при которой Двоичный транслятор следит за частотой исполнения региона, и, если регион выполняется часто, проводится рекомпиляция этого региона с изменением его кода во время исполнения. В наиболее совершенных двоичных трансляторах может иметься до 4-5 последовательных уровней оптимизации региона.

См. также[править | править вики-текст]

Примечания[править | править вики-текст]

  1. См., например, исходный код Doom Legacy, функция ASM_PatchRowBytes.
  2. Касперски, абзац с "Процессоры семейства Pentium .."

Ссылки[править | править вики-текст]