Всем хай дорогие читатели! В том числе и Хабр, спасибо отдельно :) В прошлой статье мы разбирали основы реверсинга, ну так давайте снова повторим, и на этот раз крякнем еще один какой-нибудь CrackMe! Но уже посложнее, уровня 2.0.
Главное помните, что реверсинг дело тонкое! Всё что будет показано ниже только в целях обучения. Важно иметь только и только чистые намерения! А то чпоки чпоки будет.
Подготовка
Думаю вы помните как пользоваться Ghidra или хотя бы её интерфейс? Если нет, вот, перечитайте ту часть где пишется о том как использовать этот инструмент https://habr.com/ru/articles/988388/
В любом случае, не филоним, и начнем!

Вот задачка - https://crackmes.one/crackme/68a153668fac2855fe6fb67b
Шо тут у нас? Бингус... Нельзя что б он взорвался? Ну ок, постараемся :)
Давайте запустим его?
./bingus
Бингус взорвался...

Ну ладно, давайте чутка серьёзнее. Откроем бинарник в Ghidra!

Тааак... Это входная точка, это не main , ну точнее не та функция где происходит само мясо, поэтому поищем его в Symbol Tree. Но здесь кстати, уже можно увидеть что Бингус принимает параметры, то есть аргументы, а значит надо что-то ввести сразу после ./Bingus ну как аргумент, думаю поняли.

Видим много всяких функций... Бла бла бла, тут всё лишнее кроме этих 4 синих функций. Нажимаем на каждую и посмотрим что в каждой из них!
FUN_00101020Здесь пусто...FUN_00101090Тут тоже пусто...FUN_001010c0Да что-ж такое, и здесь пусто...
ОПА! Нашли! FUN_00101149 Здесь какое-то месиво происходит...

Ну... Давайте по порядку!

Это проверка... Хм... Оно принимает два параметра, первое это понятно ./Bingus то есть argc, а вот второе уже скорее всего будет нашим паролем... Всех этих указателей кстати бояться не стоит, на них по сути даже внимания обращать не нужно. param_2 + 8 это argv[0], то есть первый элемент второго аргумента, а здесь у нас (param_2 + 8) + 1 это argv[1], второй элемент. Тут идёт проверка, равны ли argv[0] и argv[1]. Дальше у нас идёт проверка по длине второго параметра, должно быть только два символа, вполне понятно правда? Здесь короче можем понять что аргумент после ./Bingus состоит всего из двух символов, и то что эти символы должны быть одинаковыми, иначе... Иначе не выживет Бингус

Ну тут я кстати для удобства некоторые переменные переименовал, чтобы длинные local_XX не мозолили глаза, а так почитабельнее будет :)
Что тут у нас... изначально result равен 0x66 это у нас 102 в decimal, десятичной системе. Дальше идет цикл... Изначально я не понимал что он делает, но тут я заметил что строка "This is a red herring" это попытка отвлечь. На деле же, в Си string как отдельный тип данных как таковой не существует, есть массив символов! И оно перебирается и каждый раз прибавляется к result! Запомним этот момент, пойдём дальше...

Тут уже последняя проверка идёт... если result который уже после цикла + переведенная в int тип argv[1] + argv[0] переведённый в int не будет равен 0x8c5 это 2245 в decimal, то Бингус взорвется... Хмм...
Попытка решить
Окей, весь код мы разобрали, попытаемся решить! Тут без скриптинга не обойтись... Я принципиально буду писать скрипты на C, но вы можете то же самое написать на любом другом удобном вам ЯП.
Короче, к делу. Нужно выяснить длину это строки... Воспользуюсь ИИ чтоб узнать размер строки, и оно пишет что это 21... сумма равна 1982, прибавляем к ней result это +102 это значит что result равен 2084. Отнимаем условное значение 2245 от полученного 2084:
Сумма двух символом должна быть 161! Так, стоп... 161/2 = 80.5, такого символа в табилце ASCII не существует... Значит не подходит... Даже если найти символы с 80 и 81 это 'P' и 'Q' все равно символы разные, а нам нужны одинаковые...

Где-то мы ошиблись... Давайте еще раз пересчитаем... А! Походу ИИ забыл учесть что в Си, в массиве символов есть нулевой оператор '/0' и его тоже нужно учесть! Ну или ИИ реально ошиблось в расчетах, это не очень хорошо. Пересчитаем это вручную... Напишем скрипт?
#include <stdio.h>
int main() {
int result = 0;
char arr[22] = "This is a red herring"; //Оказывается здесь не 21, а 22, с учетом нулевого оператора
for (int i = 0; i < 22; i++) {
result += (int)arr[i]; //прибавляем каждое значен��е символа в int к результату...
}
printf("%d\n", result); // распечатаем
return 0;
}
И запустим ./a.out Выведет: 1919! Это у нас result после цикла, к нему надо прибавить еще и 102, изначальное в том бинарнике result был равен 102-м верно?
Окей, мы подходим к разгадке постепенно... 0x8c5 это 2245, давайте теперь от него отнимем новое полученное значение!
Делим на два, так как символов две:
Вот! Без остатка, значит это то что нам нужно! Посмотрим что это за символ...
#include <stdio.h>
int main() {
int result = 112;
printf("%c\n", result);
return 0;
}Тут мы проверим что это за символ, сконвертировав его код из цифр в символ char... Выведет 'p'!
Результат
Окей, мы знаем что символ это 'p', что символов всего должно быть 2 и они должны быть одинаковы... Всё сходится! Проверим... ./bingus pp

ДА! Мы смогли спасти Бингус и он выжил!
Небольшая вставка: Я тут поэксперементировал с скриптом, и специально уменьшил размер строки до 21 в условиях цикла и оно всё равно вывело 1919. Походу ИИ реально ошибся в расчетах. Поэтому будьте осторожны, когда доверяете языковым моделям некоторые задачи. Нулевой оператор ведь всё равно 0, поэтому он никак бы не влиял.
Выводы
Ну что? Думаю вы поняли как мы подобрали пароль к этому бингусу! Это было довольно легко!
Важный момент тут конечно в том что ИИ! Ошиблась. Поэтому не стоит ей сильно доверять, особенно в программировании. Все мы знаем вайб кодеров верно? Привет им кстати.
Снова скажу, что реверсинг дело тонкое. Относитесь к этому с умом! И тогда оно даст вам больше чем вы думаете! Нет, серьезно. Я когда решил эту задачу с Бингусом меня часа 2 штырило дофамином мол, знаете этот эффект типа "ААА! ПОНЯЛ!"? Вот это оно.
Спасибо всем за внимание, практикуйтесь, практикуйтесь и еще раз практикуйтесь. Ну и книжки не забывайте читать. Чтоб как говорится "Не хворать!". Всем пока :-)
