Разбор реверса с Я-Профи магистрского уровня. Плюс пару слов о форензике.
@ Rakovsky Stanislav | Saturday, Dec 12, 2020 | 7 minutes read | Update at Saturday, Dec 12, 2020

Похоже, в этом году составители тасков решили сильно сжалиться над участниками-магистрами. В прошлом году было в разы сложнее (и одновременно на столько же увлекательней!)

mindswiper

pyinstaller

pcap

reverse, 15 баллов > mindswiper

Вам в руки попала захватывающая игра. Пройдите все её уровни.
За один из них (Flag level) вам дадут строчку с ответом.
Внимание! За остальные уровни ответы неверные.
Идентичные версии игры предоставлены для трёх
операционных систем (https://yadi.sk/d/FHosjxEPEXf_Sg).
Формат ответа: itmo{...}

Пятый вариант

Состав архива

Вас посетил товарищ Golang

Обычно, как и в случае с Delphi, пользовательске функции лежат в конце

Прощальная корова

Описание задачи: лабиринт с бомбами и флагами. Бомбы и сами флаги невидимы.

Сам лабиринт

Условия, отвечающие за выбор. Четвертый вариант работает со вторым файлом карт.

В теле игры можно увидеть интересные строки. Они раньше были двордами, но R делает свою работу

В общем, эта задача хороша своим обилием возможных решений. Мы можем:

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

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

Будем использовать тайную технику по поверхностному реверсу - опираться на print-ы, хехе.

Самое для нас интересное - это по-быстрому получить карту и пройтись по ней, это дело пяти минут. Всё-таки Golang не очень приятно реверсится без плагинов, которые бы разбирали структурки, поэтому залезать вглубь - трата времени.

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

Попався!

Первым элементом этой структуры является карта предметов (без стен).

Дампим через Tab+E
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
|             Y |       |           |                   | F | 1
+   +---+---+   +   +   +   +   +---+   +   +---+---+   +   +
|   |       |   |   |   |   |           |   |   |       |   | 2
+   +   +   +   +   +   +   +---+---+---+   +   +   +---+   +
|   | F |   |       |   |       |           |           |   | 3
+   +---+   +---+---+   +---+   +   +   +---+---+---+   +   +
|       |         F |     F |   |   |   | F     | F         | 4
+---+   +   +---+---+   +---+   +   +---+---+   +---+---+---+
|       |       |       |       |           |               | 5
+   +---+   +   +   +---+   +---+---+---+   +---+---+---+   +
|   |       |   |   | F |           |       | F |           | 6
+   +---+---+   +   +   +---+   +---+   +   +   +   +---+---+
|           |           |       |       |   |   |   | F     | 7
+   +---+   +---+---+---+   +---+   +---+   +   +   +---+   +
|       |       |           | F |   |       |   |       |   | 8
+---+---+---+   +   +---+---+   +   +   +---+   +---+   +   +
|               |           |   |   |   |         F |   |   | 9
+   +   +---+---+---+---+   +   +   +   +   +---+---+   +   +
|   |   |               |   |   |   |       |       |       | 10
+   +   +   +---+   +---+   +   +   +---+---+   +   +---+   +
|   | F |       |       |   |   |               |           | 11
+   +---+---+   +---+   +   +   +---+---+---+---+---+---+---+
|       |       | F |       |       | F |       |           | 12
+---+   +   +---+   +---+---+   +   +   +   +   +   +---+   +
|       |                       |   |       |       |   |   | 13
+   +---+---+---+---+---+---+---+   +---+---+   +---+   +   +
|                       |       |   |       |           |   | 14
+   +---+---+---+---+   +   +   +   +   +   +---+---+---+   +
|                   |       |           |                   | 15
+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+

###Y####B#####F 1
###########B### 2
#F############# 3
####F#F##BF#F## 4
############### 5
#B###F##B##F### 6
#############F# 7
#B#####F####### 8
############F## 9
#####B######### 10
#F############# 11
####F####F##### 12 
#############B# 13
############### 14
####B########## 15

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

Вот и решение

Флаг пятого варианта: itmo{b51a26a7b36730ea06ec01be2518232c}


reverse, 15 баллов > pyinstaller

Вас просят разобраться с программой на языке Python 3.7, в которой что-то намудрили
(https://yadi.sk/d/XQ7mLhEVDIiITw). Мало того, что она собрана с помощью Pyinstaller для
запуска на Linux, так ещё и весь функционал почему-то недоступен.
Но с её помощью был зашифрован флаг, который вам очень нужно расшифровать.
Формат ответа: itmo{...}
Рекомендуемое ПО: для решения этого задания Вам понадобится Python 3.7, а так же модули
pyinstaller и uncompyle6, которые можно установить при помощи команды
pip3 install pyinstaller uncompyle6

Пятый вариант

Структура таска:

.
├── flag.enc
└── main
    ├── _bisect.cpython-37m-x86_64-linux-gnu.so
    ├── _blake2.cpython-37m-x86_64-linux-gnu.so
    ├── _bz2.cpython-37m-x86_64-linux-gnu.so
    ├── _codecs_cn.cpython-37m-x86_64-linux-gnu.so
    ├── _codecs_hk.cpython-37m-x86_64-linux-gnu.so
    ├── _codecs_iso2022.cpython-37m-x86_64-linux-gnu.so
    ├── _codecs_jp.cpython-37m-x86_64-linux-gnu.so
    ├── _codecs_kr.cpython-37m-x86_64-linux-gnu.so
    ├── _codecs_tw.cpython-37m-x86_64-linux-gnu.so
    ├── _ctypes.cpython-37m-x86_64-linux-gnu.so
    ├── _datetime.cpython-37m-x86_64-linux-gnu.so
    ├── _hashlib.cpython-37m-x86_64-linux-gnu.so
    ├── _heapq.cpython-37m-x86_64-linux-gnu.so
    ├── _md5.cpython-37m-x86_64-linux-gnu.so
    ├── _multibytecodec.cpython-37m-x86_64-linux-gnu.so
    ├── _opcode.cpython-37m-x86_64-linux-gnu.so
    ├── _pickle.cpython-37m-x86_64-linux-gnu.so
    ├── _posixsubprocess.cpython-37m-x86_64-linux-gnu.so
    ├── _random.cpython-37m-x86_64-linux-gnu.so
    ├── _sha1.cpython-37m-x86_64-linux-gnu.so
    ├── _sha256.cpython-37m-x86_64-linux-gnu.so
    ├── _sha3.cpython-37m-x86_64-linux-gnu.so
    ├── _sha512.cpython-37m-x86_64-linux-gnu.so
    ├── _socket.cpython-37m-x86_64-linux-gnu.so
    ├── _ssl.cpython-37m-x86_64-linux-gnu.so
    ├── _struct.cpython-37m-x86_64-linux-gnu.so
    ├── base_library.zip
    ├── binascii.cpython-37m-x86_64-linux-gnu.so
    ├── crypto.so
    ├── grp.cpython-37m-x86_64-linux-gnu.so
    ├── libbz2.so.1.0
    ├── libcrypto.so.1.0.0
    ├── libffi.so.6
    ├── libpython3.7m.so.1.0
    ├── libreadline.so.6
    ├── libssl.so.1.0.0
    ├── libtinfo.so.5
    ├── libz.so.1
    ├── main
    ├── math.cpython-37m-x86_64-linux-gnu.so
    ├── pyexpat.cpython-37m-x86_64-linux-gnu.so
    ├── readline.cpython-37m-x86_64-linux-gnu.so
    ├── resource.cpython-37m-x86_64-linux-gnu.so
    ├── select.cpython-37m-x86_64-linux-gnu.so
    ├── termios.cpython-37m-x86_64-linux-gnu.so
    ├── unicodedata.cpython-37m-x86_64-linux-gnu.so
    └── zlib.cpython-37m-x86_64-linux-gnu.so

1 directory, 48 files
> file ./main/main
./main/main: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=294d1f19a085a730da19a6c55788ec08c2187039, stripped

Используем официальный faq для работы pyinstxtractor’а с линёвыми бинарями.

Рекомендую использовать ту же версию Python или проставить магию в хекс-редакторе из проекта pycdc, иначе у вас будет неправильная магия, а различия в минорных версиях в байт-коде есть, поэтому можно нарваться на ошибки или неправильную интерпретацию. Не хотим же потратить пару часов на разбор, почему условие какое-то кривое?

Содержание:

import sys
from art import *
from ctypes import *
import cowsay, argparse
from sys import platform
shared_lib_path = './crypto.so'
if platform.startswith('win32'):
    shared_lib_path = './crypto.dll'
else:
    try:
        lib = CDLL(shared_lib_path)
    except Exception as e:
        try:
            print(e)
        finally:
            e = None
            del e

    art1 = text2art('encrypt like a pro')
    print(art1)
    print('* in a pro version')
    N = 9237641547581911336881323875775007720067963975755521934196540843273520595296207

    def pad(s, n):
        if len(s) < n:
            cnt = n - len(s)
            return s + bytes([cnt]) * cnt
        return s + bytes([n ** 2]) * n ** 2


    def unpad(s):
        return s[:-int(s[(-1)])]


    def gamma_generator():
        seed = 76473342171049830059
        while True:
            yield seed
            seed = pow(seed, 2, N)


    def encrypt(filename, out):
        try:
            data = open(filename, 'rb').read()
        except Exception as e:
            try:
                cowsay.stegosaurus('Error during file open')
                return
            finally:
                e = None
                del e

        if len(data) > 0:
            cowsay.stegosaurus('Encryption is not supported in demo version')
            return
        edata = b''
        passwords = gamma_generator()
        for bt in data:
            ebt = bt ^ next(passwords) % 256
            edata += bytes([ebt])

        with open(out, 'wb') as (w):
            args = create_string_buffer(pad(edata, 100))
            result = lib.make_enc(args, 100)
            w.write(result)
        cowsay.tux('Encrypted to ' + out)
        return result


    def decrypt(filename, out):
        try:
            data = open(filename, 'rb').read()
        except Exception as e:
            try:
                cowsay.stegosaurus('Error during file open')
                return
            finally:
                e = None
                del e

        if len(data) > 0:
            cowsay.stegosaurus('Decryption is not supported in demo version')
            return
        args = create_string_buffer(data)
        result = lib.make_dec(args, 100)
        ddata = b''
        passwords = gamma_generator()
        for bt in unpad(result):
            dbt = bt ^ next(passwords) % 256
            ddata += bytes([dbt])

        with open(out, 'wb') as (w):
            w.write(ddata)
        cowsay.tux('Decrypted to ' + filename)
        return ddata


    if __name__ == '__main__':
        lib.make_enc.restype = c_char_p
        lib.make_enc.argtypes = [c_char_p, c_int]
        lib.make_dec.restype = c_char_p
        lib.make_dec.argtypes = [c_char_p, c_int]
        parser = argparse.ArgumentParser(description='Encrypt and decrypt files')
        parser.add_argument('--action', required=True, help='action (encrypt, decrypt)')
        parser.add_argument('--infile', required=True, help='input file')
        parser.add_argument('--outfile', required=True, help='output file')
        args = parser.parse_args(sys.argv[1:])
        if args.action == 'encrypt':
            result = encrypt(args.infile, args.outfile)
        else:
            if args.action == 'decrypt':
                result = decrypt(args.infile, args.outfile)
            else:
                cowsay.stegosaurus('Sorry. No such action')

Видим использование внешней зависимости - crypto.so, тогда нужно проверить и ее, вдруг она проверяет окружение и искажает флаг.

Заодно посмотрите на код - его нужно немножечко полечить от жадности.

До

После

Выходной файл уменьшился на три байта, что звучит законно.

 . .
  ...
\~~~~~/
 \   /
  \ /
   V
   |
 -----
  itmo{AAE796626285F66}
------------------------

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

Давайте посмотрим, что творится в либе.

Точки входа

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


forensics, 11 баллов > pcap

Очень простая. Чистый траффик по брутфорсу, Basic Auth. При успешной аутентификации сервер возваращает зашифрованный архив. Пароль - в хэдере Basic Auth под b64. Задача очень легкого, немагистрского, уровня.


author, editor: Rakovsky Stanislav, Unicorn CTF

Cats!

Under construction

Wow! Flipable!

Hello from another side of the Moon!

Looking for a flag? Okay, take this:
LFXXKIDBOJSSA43XMVSXIIC6LYQCAIA=

About me

Under construction. You can try to contact me and fill this field… haha… ha…