Всем снова привет! В том числе и Хабру. Как вы? Отдохнули? Ну раз отдохнули, давайте переходить к новой теме!

Но перед этим обязательно должен напомнить, что реверс-инжиниринг дело тонкое! Используйте всю приведённую информацию только с благими намерениями! Нет, ребят, я серьезно, думаете что ничего не будет если "А дай-ка посмотрю что в файле конкурента... и изменю что-нибудь под себя..."? Даже если вы начинающий, если вы так думаете, то лучше закройте этот мануал. А для остальных, добро пожаловать!

Ghidra и Debugger

Начну с того, что вообще такое Debugger?

Debugger или Отладчик, это такая программа, подобна Code Browser, но имеет существенные отличия. Да, она тоже может быть использована как окно для анализа бинарника. Да, она тоже может быть использована для статического анализа. Но! Главное отличие в том, что Отладчик (Само название, по сути говорит что этот инструмент делает) может изменять байты в бинарнике прямо в рантайме (Run-time, это состояние когда программа например ./Something.exe выполняется процессором на данный момент, прямо сейчас), или что более правильнее звучит, изменять логику самого бинарника, не пытаясь восстановить исходник полностью, чтобы обойти системы защиты. Также дебаггер позволяет ставить BreakPoint (Точки остановки для программы) чтобы проверять различные места в памяти, пытаться восстановить кусочки исходника, отслеживать ячейки памяти, регистры процессора, сегменты программы и тому подобное. То есть Дебаггер работает с "живой" программой, внимательно наблюдая как она себя ведёт.

Безусловно, в Ghidra есть отдельный Debugger (Ghidra версии 11.4.2 и выше), и он тоже может послужить хорошим инструментом! НО! Ghidra изначально создавался как инструмент для статического анализа. То есть, посмотреть что в бинарнике без использования дебаггинга и рантайма. Сейчас, Ghidra вполне усовершенствовалась, но к сожалению, или к счастью, не знаю, тут уж вы решайте, мы будем использовать совсем другой инструмент для отладки (скорее редактирования) бинарников. Не бойтесь, инструмент не сложный, вполне понятный, и тем более Ghidra здесь поможет нам, и её закрывать не стоит, ведь она еще поможет нам найти то место которое нужно "отдебажить" (Пропатчить на деле) :)

Патчинг

Патчинг - это процесс изменения бинарника. Есть отличие от отладчика, в том что патчинг работает с "мертвой" программой, просто с самим бинарником, не в рантайме. Патчинг позволяет СНАЧАЛА изменить логику программы, потом запустить уже пропатченный бинарник. Инструменты для патчинга много, но в основном мы будем пользоваться hex-редактором.

HexEdit

HexEdit - это инструмент, вообще по идее который не является полноценным дебаг инструментом, но который позволит редактировать бинарник в формате hex чисел. То есть, редактировать сам бинарник в hex формате не пытаясь восстановить исходник полностью или проверять что-либо с помощью рантаймов или BreakPoint'ов. Инструмент на самом деле легкий в использовании, к тому же сам инструмент консольный, то есть работает прямо из терминала.

Давайте скачаем этот инструмент?

sudo apt install hexedit

Тут кстати не забудьте, что у вас может быть другой пакетный менеджер, в зависимости от вашего дистрибутива! У меня Mint, значит я буду использовать apt

Принимаем всё что оно может у нас спросить и ждём... После того как оно установится, подготовьте Ghidra открыв его (Думаю вы помните как?).

Используется инструмент просто:

hexedit бинарник

Эта команда сразу запускает hex-редактор, вы увидете много всяких строк, букв, цифр и тому подобное, сейчас объясню что тут к чему:

00000000   7F 45 4C 46  02 01 01 00  00 00 00 00  00 00 00 00  .ELF............
  1. Первое это у нас адрес байтов. Ну их порядковый номер! 7F это 0x0 по порядку идет от нуля до 0xXXXXXX, в зависимости сколько уже весит сам бинарник. Соответственно 45 это 0x1, 4C это 0x2 и так далее. Взгляните вниз! Там есть строчка 0x0/0xXXXX, это облегчение, программа сама показывает какой это байт по счету. Переместите курсор (Управление осуществляется стрелками), подвигайте его и увидите как это число меняется! Адрес байтов же, тот который слева 00000000 строка которая показывает сколько обычно в одной линии байтов (их 16 в одной строке), только учтите что эта строчка тоже в hex формате, не удивляйтесь, что байтов 16, а снизу написано 00000010, 10 в hex это 16 в decimal. Нужна эта строка, чтобы облегчить ориентирование, чтобы не искать отдельный байт в точности, строка позволяет найти нужное тысячное число, нужную сотню, нужный десяток чтобы в конце найти ту линию байтов где находится тот байт который мы должны пропатчить. (Надеюсь поняли😅) Если утрировать, оно нужно чтобы облегчить ориентирование по бинарнику.

  2. Второе у нас, это сами байты. Их 16 в одной линии. Пара чисел это один байт. Одно число в hex кодируется как 4 бита, 4 + 4 (так как цифр две) получается 8 бит, это 1 байт. В этой линии происходит всё то что должен исполнить процессор, проще говоря это опкоды. Ну, что-то на подобии листинг окна в Ghidra, тут происходит само "мясо" программы, то что именно исполняется, всякие инструкции, библиотеки, и всё такое, опять же во всё вникать не будем, мы будем находить только то что нам нужно будет. Но сейчас не об этом.

  3. Третье это у нас, ASCII представление этих кодов. Что я имею ввиду? Ну посмотрите на эти опкоды:

45 4C 46

Ничего не напоминает? А если перевести эти числа из hex в char в соответствии с ASCII таблицей? 45 это - 'E', 4C это - 'L', 46 это - 'F'. А точки, это непечатаемые управляющие символы (7F это DEL - Delete из таблицы ASCII), либо п��стышки (00 - это просто заполнение, на всякий случай, для нужд процессора, или может быть в будучем вы что-то собираетесь изменить в коде.), а некоторые имеют отдельные пары байт, за них лучше почитать где-нибудь в другом месте, например в https://www.industrialnets.ru/files/misc/ascii.pdf

Думаю порядок разобрали, что какая строка за что отвечает, что означает, бла бла бла, где Дебаг (Патч)? Подождите, не всё сразу. Давайте напишем небольшой бинарник, принципиально я буду делать это на C. Хочу сделать код стильным поэтому украшу его слегка!

#include <stdio.h>
#include <string.h>

#define RED   "\x1B[31m"
#define GRN   "\x1B[32m"
#define RESET "\x1B[0m"

int main() {
  char Uvar1[8]; // создадим наш input
  printf("Enter password: ");
  scanf("%s", Uvar1);

  int Ivar1 = strcmp(Uvar1, "password"); // будем сравнивать через strcmp, так легче

  if (Ivar1 == 0) {
    printf(GRN "ACCESS GRANTED!" RESET "\n");
  } else {
    printf(RED "ACCESS DENIED!" RESET "\n");
  }
  return 0;
}

Код спрашивает у нас пароль из 8 символов в типе char (Вспоминаем что в Си нету типа string как типа данных как таковой, есть массив символов), читаем то что ввёл пользователь, сравниваем строки с нашим паролем и в зависимости от того что скажет результат strcmp, выводим то или иное сообщение. Strcmp при обнаружении что наш введённый пароль и строка "password" одинаковы, прям посимвольно, strcmp вернёт нам 0. А дальше в конструкции if-else мы проверяем, правильный ли пароль или нет.

Debugging или правильнее "Патчинг"

Мы подходим к самому вкусному! Сначала давайте скомпилируем тот код, и откроем полученный бинарник в Ghidra:

Тут конечно, уже можно понять что пароль виден, но, давайте представим что пароля тут не видно и сколько бы способов мы не пробовали, что бы мы не вводили, всегда выводит "Доступ запрещен" :) Тогда на очередь идёт одна из сторон реверсинга - Патчинг!

Сейчас, окно листинга или декомпилятора нам вообще не нужно, нам нужен байтовое представление. Тут снизу было Bytes: test , это окно байтов, давайте зайдем в него. Но перед этим, стоит найти адрес тех байтов которые нам нужны. ищем тут конструкции if-else, курсором можно выделить if (iVar1 == 0) и посмотреть в окно дизассемблера:

Зеленоё потому что оно выделено. Ищем инструкцию CMP и JNZ они тут видны. Нам нужно поменять логику:

Мы вводим правильный пароль -> доступ разрешен. Нам нужно поменять логику чтобы при вводе любого другого пароля доступ был тоже разрешен.

Здесь инструкцию JNZ (Jump IF Not Zero) надо инверсировать и поменять на JZ (Jump IF Zero). Тут логика проста, что CMP сравнивает то что в памяти (введённые данные пользователя) и 0x0, это просто 0. JNZ здесь как бы говорит "Прыгни на следующую инструкцию если результат этого CMP сравнения не равен 0". Тогда покажет что доступ запрещён.

Для начала, смотрим на первый столбец где находится сама инструкция JNZ, это 00101230, нам нужны только последние 4 цифры. А также, нам нужны числа после неё: 75 11 это опкоды. По сути нам нужен только байт 75 в опкодах это как раз инструкция JNZ, и в hex редакторе её нужно поменять на 74 что соответсвует номеру инструкции JZ. Число 11 здесь для ориентировки, чтобы не перепутать.

Сделаем это?

hexedit test //откроем бинарник через hexedit

Что-то подобное мы увидим:

Окно Hexedit'а
Окно Hexedit'а

Выглядит страшно правда? Я тоже испугался. Смотрим на столбец слева, самый первый, нужно найти тот адрес у которого последние 4 цифры это 1230.

Опкоды в HexEdit окне
Опкоды в HexEdit окне

Нашли! Остальные байты нам трогать ни к чему, нам нужны именно те которые были в том окне листинга, число 11 после 75 это просто подтверждение:

Те же Опкоды в листинге Ghidra, для подтверждения.
Те же Опкоды в листинге Ghidra, для подтверждения.

Сравнивая их мы понимаем что это две абсолютно идентичные строчки опкодов :)

Дело за малым, наводим курсор стрелками на число 5 и меняем на 4 просто нажмите на число 4, тогда инструкция JNZ поменяется на JZ. Hexedit выделит жирным шрифтом то что вы изменили, это для удобства. Всё! Больше ничего менять не нужно, нужно сохранить и выйти, это комбинация клавиш Ctrl + X. Оно спросит Да\Нет\Отмена мы нажимаем 'y' и подтверждаем. Всё! Дальше пробуем запустить и ввести всякую ересь:

./test

Готово! Мы ввели неправильный пароль и он дал нам доступ!

После таких манипуляций строчка if (Ivar1 == 0) будет выглядеть вот так: if (Ivar1 != 0)

Ирония в том что если ввести "правильный" пароль из исходника то можно получить вот такой фокус:

Пароль то по идее должен быть правильный, он же как бы записан в бинарнике верно?
Пароль то по идее должен быть правильный, он же как бы записан в бинарнике верно?

Прикольно :)

Выводы

Ну что-ж? Мы узнали что такое hex-редактор, узнали зачем он, как он, один из представителей таких инструментов - hexedit, поняли разницу между отладкой и патчингом, не оставили Ghidra в стороне, а главное смогли пропатчить свой код и понять куда смотреть!

Вообще изначально я перепутал понятия "Дебаггинг" с "Hex Редактором", но надеюсь это вам не помешало.

Используйте это опять же в благих целях, без злого умысла, тогда всё будет чики-пуки.

Небольшая задачка на прощание, Что это за строка?
47 6F 6F 64 20 4C 75 63 6B 21

Увидимся в следующих статьях!