Метка: patch

Что такое патч кода: как один байт может изменить программу

Если говорить просто, патч кода — это небольшое целевое изменение программы, которое исправляет ошибку или меняет её поведение.

В обычной разработке patch часто представляет собой набор изменений исходного кода: какие строки были добавлены, удалены или исправлены. Такие патчи используются в Git, при code review и исправлении ошибок.

Но в системном программировании и reverse engineering под патчем кода часто понимают другое — изменение уже скомпилированной программы на уровне машинных инструкций.

Иногда для этого достаточно изменить всего один байт.

Как один байт может изменить работу программы

Представим следующий x86-код:

test eax, eax
jne short loc_continue

Команда jne выполняет переход только при определённом результате проверки.

Но что произойдёт, если заменить условный переход jne на безусловный jmp?

test eax, eax
jmp short loc_continue

В машинном коде короткий jne и короткий jmp имеют разные opcodes. В подходящем случае изменение одного байта превращает условный переход в безусловный.

Исходный код программы не изменился.

Программа не была заново скомпилирована.

Но логика выполнения уже другая.

Это и есть один из простейших примеров binary patch — бинарного патча кода.

Зачем вообще патчить готовый код

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

Нет исходного кода

Допустим, ошибка находится в сторонней DLL или другом компоненте, исходники которого недоступны.

Можно ждать исправления от разработчика.

А можно найти проблемное место в дизассемблере и изменить несколько инструкций.

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

Нужно изменить поведение функции

Патч позволяет:

  • убрать ненужную проверку;
  • изменить условие перехода;
  • заменить вызов функции;
  • перенаправить выполнение в собственный обработчик;
  • добавить дополнительную логику.

Вместо изменения всей программы модифицируется конкретная точка выполнения.

Нужен перехват функции

Один из классических вариантов выглядит примерно так:

jmp my_handler

Начало функции заменяется переходом в собственный код.

Теперь управление получает my_handler.

Он может записать параметры в лог, изменить данные, выполнить дополнительную проверку или полностью заменить исходное поведение.

Именно на таких техниках построено множество систем перехвата и инструментирования кода.

Нужно исправить работающий процесс

Отдельное направление — hot patching, когда изменяется код уже загруженного процесса.

В некоторых системах это позволяет применить исправление без перезапуска процесса или замены всего исполняемого файла.

Основные техники патчинга кода

Inline patch

Самый прямой вариант.

Машинные инструкции изменяются непосредственно в теле функции.

Например:

jne short error

можно заменить подходящей по размеру инструкцией или последовательностью инструкций.

Inline patch удобен для небольших изменений, но требует точного понимания машинного кода.

NOP patch

Инструкция NOP не выполняет полезной операции и позволяет фактически убрать часть кода.

Например, несколько инструкций можно заменить NOP-последовательностью, сохранив занимаемое ими место.

Так часто отключают проверки или ненужные вызовы.

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

Jump patch

В код записывается переход:

jmp my_handler

Выполнение перенаправляется в другую область памяти.

Собственный обработчик может полностью заменить исходную функцию или выполнить дополнительную работу.

Trampoline

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

Общая схема выглядит так:

  1. Сохраняются инструкции, которые будут перезаписаны.
  2. В начало функции устанавливается переход на обработчик.
  3. Сохранённые инструкции помещаются в отдельную область памяти.
  4. После них выполняется переход обратно в оригинальную функцию.

Полученная область кода называется trampoline.

Подобную схему, например, использует Microsoft Detours.

Patch и hook — это не одно и то же

Эти понятия часто смешивают.

Patch — изменение существующего кода или данных программы.

Hook — перехват вызова или события.

Патчинг машинных инструкций может использоваться для установки hook. Например, начало функции можно заменить инструкцией jmp.

Но существуют и другие способы перехвата.

В Windows вызовы импортированных функций связаны с Import Address Table — IAT. Изменение адреса функции в такой таблице позволяет перенаправить вызов без перезаписи первых ASM-инструкций самой функции.

Поэтому правильнее сказать так:

patch может использоваться для реализации hook, но patch и hook не являются синонимами.

Почему патчинг машинного кода опасен

Изменить один байт легко.

Понять, какой именно байт можно изменить, значительно сложнее.

Инструкции x86 имеют разную длину

Нельзя считать, что одна строка дизассемблера всегда занимает одинаковое количество байт.

Если перезаписать только часть инструкции, процессор начнёт декодировать повреждённую последовательность байтов.

Результатом обычно становится неправильное выполнение кода или падение процесса.

Нужно учитывать относительные адреса

Инструкции перехода и вызова могут использовать относительные смещения.

Простое копирование такой инструкции в trampoline не всегда сохраняет её первоначальный смысл.

Адреса приходится пересчитывать или использовать другую последовательность инструкций.

Кодовая память обычно защищена от записи

Страницы памяти с исполняемым кодом обычно нельзя просто изменить обычной операцией записи.

В Windows защита страниц может временно изменяться с помощью VirtualProtect.

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

Есть проблема многопоточности

Представим, что один поток перезаписывает начало функции, а другой именно в этот момент начинает её выполнять.

Второй поток может увидеть код в промежуточном состоянии.

Поэтому runtime patching требует продуманной синхронизации. Недостаточно просто вызвать memcpy по адресу функции.

Версия бинарного файла имеет значение

Патч, созданный для одной версии программы, может оказаться неприменим к другой.

Компилятор изменил оптимизацию — инструкции переместились.

Добавилась функция — изменились адреса.

Вышла новая DLL — старый набор байтов больше не совпадает.

Поэтому надёжный patch должен проверять, что изменяется именно ожидаемый бинарный код.

Когда патч кода действительно полезен

Binary patching оправдан, когда:

  • нет доступа к исходникам;
  • нужно исправить сторонний компонент;
  • требуется исследовать или изменить поведение программы;
  • выполняется reverse engineering;
  • разрабатывается система инструментирования или перехвата;
  • необходимо точечное изменение машинного кода.

Но патч не является заменой нормальной архитектуре.

Если программа находится под вашим контролем и проблему можно исправить в исходниках, в большинстве случаев именно это и следует сделать.

Так что же такое патч кода

Патч кода — это целевое изменение программы, исправляющее ошибку или меняющее её поведение.

На уровне исходников patch может быть обычным набором изменений файлов.

На уровне готового бинарного кода всё становится интереснее: изменяются уже машинные инструкции, переходы и адреса.

Иногда сложный перехват требует trampoline и анализа десятков инструкций.

А иногда поведение целой функции действительно меняется заменой одного байта.

Именно поэтому binary patching остаётся важным инструментом системного программирования и reverse engineering.

Исправление ошибки в поддержке асинхронного COM при запуске приложения

В статье Асинхронный COM под Windows Vista и Windows 7 была описана ошибка, которая появилась при выходе указанных операционных систем. Для разрабатываемого (а на тот момент уже и продававшегося) программного обеспечения надежная работа асинхронного COM была очень важна. Обращения в Microsoft с просьбой внести исправления ничем не закончились. Оставался только один вариант — самостоятельное исправление

Асинхронный COM под Windows Vista и Windows 7

Технология COM (Component Object Model) развивается уже почти двадцать лет. До сих пор на ней построена работа подавляющего числа компонентов системы. Думаю, что так и будет продолжаться в ближайшем будущем. Асинхронный COM — это вариант использования этой технологии. Некоторые приложения начала 2000-х использовали его для построения высоконагруженных серверов. Все не так безоблачно для Асинхронного COM.