![]() |
Правка двоичных файлов |
![]() |
Структура файла 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#
= new Process();
Process p .StartInfo.FileName = "program.exe";
p// позволяет вводить строки в запущенную программу из кода этой программы
.StartInfo.RedirectStandardInput = true;
p.Start();
p
// вводим строку в программу
.StandardInput.WriteLine("bla bla bla");
p
// ждём её завершения
.WaitForExit();
p
if (p.ExitCode == 0)
.WriteLine("Программа завершилась успешно");
Consoleelse
.WriteLine("Программа завершилась с кодом ошибки " + p.ExitCode.ToString()); Console
