User1
Здесь могла быть ваша реклама, но пока тут реклама @ch4nnel1
Original write-up: https://hackmd.io/@osogi/Simple_Protocol
Сам таск(https://game.ctfcup.ru/tasks/948157b9-8d44-477d-afac-4b46488d89d0)
И еще гитхаб таска -> https://github.com/acisoru/ctfcup-21-quals/blob/main/tasks/reverse/simple-protocol/
Можем попробовать запустить бинарник, но нам это мало что даст, так как он просто читает что-то с ввода и потом отдает несколько байт.
Давайте посмотрим, что находится у него внутри
Странно, судя по main'у бинарь должен просто вывести hello word, однако ничего подобного не происходит и близко. Попробуем поставить бряку на entry point и запустить. Иииии… прога не дойдет до нашей бряки, но будет выполнять какой-то код, а известных мне инитов или чего-то что должно запускаться перед entry нет (на самом деле есть читайте Эпилог), вот так мистика. Ладно допустим, что путем странных манипуляций еще до захода в entry исполняется какой-то другой код. Попробуем запустить приложуху под strace, чтобы хотя бы примерно понять что и как.
По логам последнего видим, что бинарь выполняет как-то слишком много работы с памятью вначале (использует syscall'ы mmap
и mprotect
), что может подвести нас к идее, что он запакован. Но проверив с помощью die, ничего интересного не получаем.
Хммм… Окей, попробуем продебажить радаром до тех пор, пока бинарь не начнет читать наш инпут. Я использовал для этого команду dcs read
, которая запускает бинарь до вызова сискола read и ждал пока первый операнд будет равен 0, что означает что он читает из stdin
.
После окажемся где-то в районе 0x7f… адресов, давайте проверим в каком "мапе" мы очутились
Вау, сколько тут всего намапилось интересного еще до выполнения основного бинаря. Пока дебажил, еще случайно нашел пару строчек, которые содержали "upx", что приводит к мысле что в какой-то момент тут распаковывался upx файл и все эти подозрительные сегменты/мапы вызваны частично и им. Ну что же, поищем elf header
Сопоставив эти данные с известными мапами, придем к выводу, что нам скорее всего нужны мапы с unk5 по unk8, так как в них и находится бинарник, который выполняет всю "полезную" работу. Сдампим их
Попытка запуска распакованного бинаря ничего не даст,
Однако, закинув его в тот же радар, получим интересный результат, что это "живой", настоящий бинарь.
Анпакнутый elf, если кому-то нужно
Я конечно мазохист, но не настолько чтобы реверсить что-то в радаре, если это можно сделать в иде, так что погнали закинем распакованный бин в иду, и чуть пореверсим. Тут я быстро расскажу о реверсинге некоторых функций и как это делалось
Функция небольшая, но с наскоку трудно понять, что она делает. Но если же подправить и засунуть этот код в какую-нибудь gcc, чтобы потыкать его, то можно быстро определить, что это простое умножение двух чисел. Обзовем ее imul
.
Просто видно, что это бинарное возведение в степень.
В бинаре есть ряд функций имеющих похожую структуру, просто смотрим асм, какой сискол вызывают, и настраиваем функцию под его аргументы.
По тому где вызывается функция и что делает, можно легко провести параллели с сишным маллоком.
Эти две функции существуют, выполняют какую-то крипту. И так как одна похожа на перевернутую другую, то я решил проверить с помощью дебага радаром, обратные ли они, и это оказалось так. Так что было решено что одна криптит, а другая декриптит.
После реверса всех "дочерних" функций получаем примерно такой main
Из него мы можем понять, что сначала мы подаем число, которое используется для генерации "ключа". После отсылаем зашифрованные блоки, которые при расшифровке складываются в примерно такой протокол связи, по нулевому смещению находится хедер в виде 2 байт \x22\x33
.
Operation\Offset | +2 (operation code) | +3 | +7 | +9 |
---|---|---|---|---|
fopen | 1 | len of path to file (4 bytes) | filepath (len bytes) | - |
read file | 2 | some padding <=0x18 (4 bytes) | another padding (2 bytes) | how much read (4 bytes) |
close (simply close file, need for last) |
3 | - | - | - |
write data from file | 4 | len of secret (4 bytes) | secret (len bytes) |
Вообще зная протокол, написать клиент не должно составить труда. Могут возникнуть только проблемы с тем, что мы не знаем степень, в которую возводится число, используемое для генерации ключа. Но ее можно либо попробовать высчитать, на основе известных данных, либо же заслать 1 или 0, так как что первое, что последнее в любой степени дают сами себя -> мы всегда будем знать, число которое использовалось для генерации ключа.
И так же возникают трудности с автоматическим декриптом и инкриптом сообщений. Что бы решить эту проблему у меня в голове было 3 способа:
Первый вариант я не выбрал так как, до сих пор не могу в фриду. Второй вариант мне не понравился, так как нужно писать сокеты внутри so, либо шаманить с обменом инфой между so и сторонним приложением. Так что я выбрал самый безболезненный, но костыльный вариант - rzpipe. И конечно буду кодить на питоне так как это быстрее всего. Разберем как же это все было реализовано. Для начала разберем конструктор и какие переменные вводятся
Дальше разберу реализацию одной из функций, остальные работают схожим образом
Полный код клиента можно найти тут P.S. Чейнд не бей за использрвание pwntools, я хотел заfb'шить таск, а это единственное, что я помнил
Вообще, изначально когда реверсил таск на ctf, я пропустил, что в протоколе идет проверка секрета и все такое, и заметил это в самом конце. Так что в райтапе тоже было решено это отложить на финал.
Вот вроде бы мы написали функции декрипта и инкрипта, все работает. Начинаем писать код для получения флага, как вдруг видим, что чтобы получить байты файла, в протоколе есть еще проверка какого-то секрета. Она реализована следующим образом.
В принципе тут почти сразу видно, что это можно взломать, так как нам известен желаемый результат (0xDF705098
) и при result << 8
последний байт 0, то можно найти среди some_array
элемент, который тоже оканчивается на тот же байт, что и желаемый результат. Дальше сделать так 4 раза (так как 4 байта) + сделать поправочку на ксор, оформить это все в скрипт и вуаля мы подобрали нужный секрет, и им оказался \xca\xdb\x88\x2d
Добавляем наш секрет в клиент, запускаем его. На локалке все робит, отлично, запускаем на сервер и получаем флаг.
Авторское решение туть -> https://github.com/acisoru/ctfcup-21-quals/blob/main/tasks/reverse/simple-protocol/README.md
Я посмотрел, потыкал почему у нас запускается какой-то другой код в начале бинаря, и прога даже не доходит до entry point'а. Как оказалось это вызвано тем что в коде был преинит участок (подробнее о всяких штуках происходящих перед загрузкой main'а можно почитать тут https://habr.com/ru/post/339698/), и радар хоть его и обнаружил, но справился плоховато: никак не обозвал эту функцию + плохо ее проанализировал - сильно ее обрезал (обрезал прям всю), в отличие от ризина, который справился с этим делом лучше
В очередной раз доказано, что ризин>радара. На самом деле у меня иногда тупит и тот и другой, так что я пользуюсь ими посменно, на одном таске могу по приколу юзать то одно, то другое.
Кого-то еще могло заинтересовать почему на картинке, превьюшке, какие-то аниме чувачки и как это связано с таском. Ну вот, это все из-за того, что я загуглил "protocol" нашел такую пикчу к какой-то игре, и мне понравилось как это выглядит.