Капибайкер

Капибайкер

Dolgikh Konstantin
Капибайкерский клуб решил устроить мотопробег и подал схему его маршрута в мэрию. Но в компьютере произошел сбой, и маршрут зациклился.
Капибайкеры уже несколько часов ездят по кругу и сильно шумят. Почините схему, чтобы пробег наконец закончился.
Программа управления гонкой: bike_track_38b400d.py

Осматриваемся

Скачиваем файл, запускаем под подходящей версией Питона и получаем ещё один файл embedded.pyc и окошко. Если кликнуть по какому-нибудь мотоциклу, то система приходит в движение:

Запуск скачанного файла

Если вглядеться, то можно заметить, что 1)все байкеры меняют своё направление при столкновении со стенкой либо на противоположное, либо, если есть стрелка, на указанное по стрелке, 2)справа внизу есть клетка финиш, в которую мы никогда не попадём при таком устройстве лабиринта.

Сам же исполняемый файл проверяет версию Питона, в которой он запущен, извлекает из себя соответствующий скомпилированный байт-код, сохраняет в отдельный файл и импортирует его. В процессе импорта он исполняется и создаёт видимое нам окно.

Заглядываем внутрь

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

Флага в открытом виде нет

Зато есть какие-то строки, включающие флаг. Давайте разбираться, что внутри, и модифицируем скрипт, заменив последнюю строчку с __import__("embedded") на obj = __import__("embedded"). И запускаем теперь файл не просто так, а выполнив код из файла перед запуском интерактивной консоли командой PYTHONSTARTUP=./bike_track_38b400d.py python3. Вероятно, на Windows это будет делаться иначе. Окно снова появляется, снова можно погонять байкеров по полю, но оно нам неинтересно. Интересно, что после его закрытия мы получаем интерактивную консоль с Питоном и загруженным модулем:

Консоль Питона с модулем

Давайте выполним help(obj) и посмотрим, что у нас есть. Видим, что в модуле есть класс Biker, ряд функций, полей и Tkinter-объекты для создания окна:

help(obj)

Тут мы можем увидеть сам лабиринт, например, и понять, что он легкочитаем и включает в себя все указатели поворотов и препятствия. Сначала я думал отредактировать лабиринт и заново создать окно, но беглый гуглёж показал, что если окно Tkinter было закрыто, то открыть заново его можно только если заново создать объект и я от этой идеи отказался, тем более что это не это самое интересное.

Лабиринт

Самое интересное это поля flag и decode_key, а также функция decodeFlag(key, encrypted_flag), которая, кажется, должна принимать именно эти поля. Давайте её вызовем и посмотрим, что будет:

Получение флага

Бинго! Задача решена! (На скриншоте выше он аккуратно скрыт, но вы должны получить валидную строчку такого же формата.)

Замечание. Уже в процессе написания этого write-up я обнаружил, что decode_key пуст. Чтобы это исправить, необходимо в окне кликнуть по байкеру, чтобы они пришли в движение, потыкались о стенки и только после этого закрыть окно. Это вызовет ошибку исполнения, но файл всё равно будет импортирован и пригоден для дёргания полей и функций, в противном случае decode_key оказывается либо пустым, либо короче необходимого, что приводит к ошибке декодирования.

Что ещё можно сделать?

Патч лабиринта

Вероятно, это решение не совпадает с тем, что задумывал автор задачи, и автор хотел, чтобы мы открыли байт-код и отредактировали лабиринт. Это решение описано, например, здесь: https://t.me/tinkoffCTFAnswers/172

Для этого можно воспользоваться, например, hexedit, найти нужные байты методом пристального вглядывания и заменить:

Замена 3E на 5E с помощью hexedit приводит к замене ^ на > в лабиринте
Запуск патченного файла допускает достижение финиша

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

Декомпиляция байт-кода

В принципе embedded.pyc можно декомпилировать и читать внутреннюю логику в каком-то виде. Для этого есть несколько инструментов различных степеней продвинутости, например, банальный модуль dis: https://docs.python.org/3/library/dis.html

С помощью него, например, можно выяснить, что функция decodeFlag внутри себя просто вызывает функцию rc4 и декодирует байты в строку:

Внутренности функции decodeFlag

Здесь метод оказался бесполезен, но потенциально этот инструмент может быть полезен в других задачах на реверс и Питон.

Report Page