![]() |
Правка двоичных файлов |
![]() |
Структура файла Portable Executable
To be written…
Процесс загрузки PE-файла
To be written…
Расписать про релокации…
Студентам, выполняющим работу - слушайте лекцию про релокации.
Создание правок с помощью отладчика
Отладчик x64dbg позволяет редактировать отлаживаемую программу “на горячую”, т.е. прямо в процессе отладки. Для этого достаточно выделить инструкцию, которую необходимо изменить, и нажать клавишу “пробел”. В появившемся диалоговом окне можно ввести новую инструкцию, которая заменит собой текущую.
Несмотря на кажущуюся простоту, правка двоичных файлов сопряжена со многими трудностями. Рассмотрим несколько примеров, чтобы дать понятие о сложностях этого процесса. Допустим, нам нужно изменить инструкцию
mov eax, 0AABBCCDDhна инструкцию
mov ax, 0EEFFhв двоичном виде первая инструкция имеет вид B8 DD CC BB AA и занимает 5 байт,
а вторая инструкция имеет вид 66 B8 FF EE и занимает 4 байта. Это означает,
что выполнив перезапись инструкции, мы получим последовательность байт
66 B8 FF EE AA, где первые 4 байта - это инструкция mov ax ..., а последний
байт AA - остаток первоначальной инструкции. Полученная последовательность
соответствует коду
mov ax, 0EEFFh
stosbЧтобы избавиться от инструкции stosb необходимо заменить её на другую
однобайтовую инструкцию - nop, которая не производит никаких действий и просто
переходит к следующей.
Приведённый пример позволяет сделать следующие выводы:
- При правке инструкций необходимо всегда учитывать размеры исходной и требуемой инструкций.
- Правка возможно только если размер исходной инструкции больше либо равен размеру требуемой.
- Лишние байты всегда можно заменить однобайтовой инструкцией
nop.
Теперь представим, что нам хочется избежать вызова нежелательной функции:
call some_bad_funcЕсли вызываемая функция имеет соглашение вызова stdcall, то затереть вызов
несколькими nop будет недостаточно. Где-то выше инструкции call находятся
инструкции push, передающие через стек аргументы вызываемой функции. До правки
стек очищался вызываемой процедурой, а после правки он останется неочищенным:
push 1
push 2
push 3
; 6 байтов nop, которые заменили собой call
nop
nop
nop
nop
nop
nopПо этой причине, вместо вставки nop необходимо вставить код очистки стека,
свободных байтов под который, к счастью, хватает:
push 1
push 2
push 3
; вместо вызова сразу чистим стек
add esp, 12
; оставшиеся 3 байта меняем на nop
nop
nop
nopНаконец, рассмотрим проблему, возникающую при правке вызовов внешних функций, адреса которых участвуют в процессе разрешения релокаций. Компиляция кода вида
...
call [printf]приведёт к поялению релокации адреса функции printf. Это означает, что даже
если перезаписать все инструкции push и call, относящиеся к вызову, то при
загрузке PE-файла последние инструкции nop будут снова заменены на реальный
адрес printf. Этот адрес будет проинтерпретирован как код, выполнение которого
скорее всего приведёт к ошибке:
; nop-ы, заменившие push-и
nop
nop
nop
; 2 nop-а, заменившие начало инструкции call
nop
nop
; 4 байта адреса релокации, которые теперь интерпретируются как инструкции
div [eax+ebx*2+3]Для того, чтобы правильно обойти такой вызов, нужно воспользоваться инструкцией
jmp и перевести выполнение программы сразу за вызов call.
Правки, внесённые во время отладочной сессии, не сохраняются между перезапусками программы и самого отладчика. Чтобы сохранить их, используется окно Patches, которое можно вызывать из меню File или комбинацией клавиш Ctrl+P. В этом окне кнопка Export позволяет создать файл-заплатку, который может быть позднее загружен в отладчик, чтобы повторить сделанные изменения заново. Кнопка Patch File сохраняет PE-файл с наложенными изменениями.
Контрольные вопросы
- Какие существуют секции в файле формата PE?
- Что такое релокации?
Задание на лабораторную работу
- В программе из лабораторной работы №5 прошлого семестра произвести такие
изменения, чтобы функция
Sleepне вызывалась и не задерживала выполнение программы. - Создать файл-заплатку, содержащий произведённые изменения.
- Перезагрузить программу в отладчике, применить файл-заплатку, сохранить изменённый .exe файл под другим именем.
- Написать программу на любом языке программирования, которая подбирает логин и пароль исходной программы методом грубой силы. Ниже приведён примерный код на языке C#.
Запуск и взаимодействие с консольными программами на языке C#
Process p = new Process();
p.StartInfo.FileName = "program.exe";
// позволяет вводить строки в запущенную программу из кода этой программы
p.StartInfo.RedirectStandardInput = true;
p.Start();
// вводим строку в программу
p.StandardInput.WriteLine("bla bla bla");
// ждём её завершения
p.WaitForExit();
if (p.ExitCode == 0)
Console.WriteLine("Программа завершилась успешно");
else
Console.WriteLine("Программа завершилась с кодом ошибки " + p.ExitCode.ToString());


