Отказ от ответственности: я хотел бы подчеркнуть, что информация, которой я поделился в этой статье, предназначена исключительно для образовательных целей. Крайне важно использовать эти знания ответственно и законно. Я преподаю в программе ALX Software Engineering, и моя цель — способствовать лучшему пониманию реверс-инжиниринга, а не поощрять какие-либо формы взлома или незаконной деятельности.

В мире разработки программного обеспечения проблемы бывают самых разных форм и размеров. Но некоторые задачи действительно проверяют наши навыки и подталкивают нас к краю наших знаний. Одна из таких задач возникла у меня во время изучения низкоуровневого программирования по программе ALX Software Engineering.

Задача была следующая: нам дали программу под названием 101-crackme. Для запуска этой программы требовался пароль, и наша цель состояла в том, чтобы создать программу, которая генерирует случайные действительные пароли для 101-crackme. Эта задача представляла собой серьезное препятствие, так как у нас не было доступа к исходному коду 101-crackme, а это означало, что нам пришлось провести обратный инжиниринг.

Реверс-инжиниринг — увлекательная, но сложная область кибербезопасности. Вам нужно не только владеть языками программирования, но и хорошо понимать язык ассемблера, особенно для архитектур Intel.

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

Затем я начал изучать отладчик GNU (GDB). Это мощный инструмент командной строки, сыгравший решающую роль в отладке и анализе программ во время выполнения. Используя GDB, я получил ценную информацию о функционировании программы, что позволило мне понять ее внутренние механизмы.

После всех новых вещей, которые я узнал, я был готов принять вызов. Теперь давайте пройдемся по шагам, которые я предпринял для решения этой задачи:

1- Тестирование программы:

Я начал с того, что запустил программу и просмотрел ее вывод на наличие неправильных паролей или аргументов. Этот первоначальный тест помог мне понять, что программа проверяла во время выполнения.

2- Функция обнаружения и установка точек останова:

На этом шаге я запустил GDB с флагом -q, чтобы пропустить информацию об авторских правах и версии. И с помощью команды «info functions» я перечислил все функции в программе.

Выделялись две функции:

  • Основная функция: основная функция любой программы на C, она служит точкой входа для выполнения программы.
  • Контрольная сумма: Хотя я не мог определить многое по одному только его названию, похоже, это функция, которая выполняет некий подсчет суммы, возможно, связанный с паролем.

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

После этого я установил вариант дизассемблирования на Intel и использовал команду «layout asm», чтобы отобразить инструкции в новом окне, выделив при этом выполняемые инструкции.

Теперь я мог запустить программу с любым случайным паролем, чтобы проследить, как она проверяет правильность введенного пароля (например, «выполнить 000»).

3- Проверка инструкций по сборке:

Во-первых, есть несколько инструкций, которые вам нужно знать, чтобы понять, что делает программа:

  • mov: это функция, которая перемещает константы правой переменной в левую.
  • add: это функция, которая добавляет значение правой переменной к левой.
  • cmp: Эта функция сравнивает содержимое двух регистров (две переменные).
  • ret: Функция, возвращающая управление основной функции.

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

С этого момента я начал выполнять следующую инструкцию (используя команду «ni»), обращая внимание на поведение программы на каждом шаге. Я продолжал выполнять следующую инструкцию, пока не дошел до сообщения «Неверный пароль», указывающего на то, что программа определила, что введенный пароль неверен.

Выполняя инструкции, я заметил 3 вещи:

  • Инструкции от 0x400576 до 0x400595 повторялись четыре раза, как если бы это был цикл. Точнее, он зацикливался на символах введенного мной пароля. Например, когда я использовал «000» в качестве тестового пароля, он зациклился три раза, а на четвертой итерации не нашел больше символов, поэтому продолжил.

  • Во время цикла программа выполняла прибавления к переменной rax, которая сначала была инициализирована значением 0. После завершения функции контрольной суммы и непосредственно перед возвратом к основной функции (до выполнения инструкции ret), в этот момент, если вы проверьте значение переменной rax, вы обнаружите, что оно равно 144. Если вы помните, я запускал программу со случайным паролем «000». Значение 0 в таблице ASCII равно 48, а если умножить 48 на 3, получится 144. Наконец, функция контрольной суммы возвращает значение rax в основную функцию.

  • В основной функции и перед печатью «Неправильный пароль» основная функция выполняла сравнение между значением, возвращаемым из функции контрольной суммы, и константой 0xad4 (что равно 2772 в десятичном виде).

Все эти признаки привели меня к предположению, что программа работает следующим образом: функция контрольной суммы вычисляет числовое ASCII-значение символов, введенных в качестве пароля, и затем передает его основной функции. Далее основная функция сравнивает эту сумму с константой 2772, и если они не равны, то пароль считается неверным.

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

4- Поиск действительного пароля для проверки:

Чтобы проверить то, что я обнаружил, мне нужно было найти пароль, для которого сумма его символов равна 2772 в значениях ASCII. Посмотрев в таблицу ASCII, я обнаружил, что символ «~» имеет значение 126, и если я умножу его на 22, результатом будет 2772.

Тада! Поздравляю, это работает.

5- Написание программы

На этом этапе последним шагом является успешное написание программы, которая генерирует случайный набор символов, сумма значений ASCII которых составляет 2772.

Создание случайного набора символов легко достижимо для начального набора. Однако важно отметить, что по мере того, как сумма приближается к 2772, необходим другой подход. Когда сумма становится слишком близкой к 2772, персонаж больше не может быть случайным. Вместо этого это должно быть пропущенное число, которое довело бы сумму точно до 2772. После печати этого символа программа должна завершиться.

Оштрафовать код можно здесь.

Вывод:

Обратный инжиниринг программы 101-crackme был сложным, но полезным опытом. Анализируя инструкции по сборке и используя такие инструменты, как GDB, я обнаружил механизм проверки пароля программы. что позволило мне успешно создать программу, генерирующую действительные пароли. Этот процесс продемонстрировал силу обратного проектирования и важность знаний в области низкоуровневого программирования. В целом, это было захватывающее путешествие, которое улучшило мои навыки и понимание разработки программного обеспечения.

Я открыт для обсуждения и обмена информацией по этой теме. Кроме того, если вы столкнетесь с какими-либо ошибками или у вас есть ценная информация, которую можно внести, свяжитесь со мной Аксаим Мохамед Амин.

В конце концов, мы все способны совершать ошибки, и сотрудничество помогает нам учиться и расти вместе.