Files
Kubok-Regionov/MEGA-router/writeup_tasks_1_2.md
2025-12-22 05:19:38 +03:00

4.7 KiB
Raw Permalink Blame History

Разбор решения MEGA-router: задачи 1 и 2

Ниже разбор решения первых двух задач. Сначала общий вход (как получить бинарник), затем отдельно по каждому флагу.

Общий вход: утечка бинарника через /ping

  • Авторизация не нужна: logged_in() просто проверяет, что в Cookie есть и username=, и password=; значения не важны (challenge/src/server_http.hpp). Альтернатива — дефолтные креды admin/admin или admin/admin888 (challenge/src/server.cpp).
  • /ping?id=... читает 4096 байт из Global.BUFFER + offset*4096 без проверки границ и отдает base64 (challenge/src/server.cpp). Падение дочернего процесса дает пустой result, сервер не падает.
  • Зацепка: зайти на /portal с любыми Cookie username=...; password=... и открыть main.js — там видно, что клиент делает POST /ping и затем многократные GET /ping?id=..., то есть это ключевая точка.
  • Так как offset — беззнаковый и - запрещен, читаем «назад» через переполнение: 4096 = 2^12, значит 2^64/4096 = 2^52. Берем offset = 2^52 - N, получаем адрес BUFFER - N*4096. Этим выходим на .text/.rodata и находим ELF.
  • Минимальный запрос, который дает первую зацепку в ответе (base64-кусок памяти) и позволяет начать сканировать ELF:
GET /ping?id=4503599627370495 HTTP/1.1
Host: <host>:31337
Cookie: username=a; password=b
  • Дальше — обычный дамп страниц и склейка в файл; можно искать строку ELF или специфичные строки вида /giv_me_please_flag_....

Задача 1 (Flag 1)

  • В бинарнике виден скрытый эндпоинт "/giv_me_please_flag_nu_ochen_nado" (challenge/src/server.cpp).
  • Делаем GET на него, ответ — редирект на /index с заголовком Flag: <base64>. Декодируем base64.
  • Дешифрование: байты зашифрованы ROTR3 после XOR с (i*4 + 0xc0). Значит, для каждого байта: ROTL3, затем XOR.
import base64

def rotl8(x, n):
    return ((x << n) | (x >> (8 - n))) & 0xff

cipher = base64.b64decode(flag_header)
plain = bytes(
    rotl8(b, 3) ^ ((i * 4 + 0xC0) & 0xff)
    for i, b in enumerate(cipher)
)
print(plain.decode())
  • Итоговый флаг: caplag{r0ut3r_p0rtals_ar3_ult1mat3ly_imp3n3trabl3_b3caus3_th3y_ar3_r3al_w3bapp}.

Задача 2 (Flag 2)

  • В логике /login видно, что для юзера hoEjB9OtHLCuDibAT6Ag вызывается gen_flag, но он защищен длинной подстрокой и MD5 (прямой вызов нецелесообразен) (challenge/src/server.cpp).
  • В gen_flag видно массив flag_byte_offsets[] и схему: берется pi_bytes.bin (2048 байт, соответствуют позициям 1_000_000..1_002_047), а флаг строится как bytes[offset - 1_000_000].
  • Поэтому нужно: (1) вытащить flag_byte_offsets[] из слитого бинарника; (2) сгенерировать байты π для диапазона 1_000_000..1_002_047. Функция get_byte — это две hexцифры π подряд. Быстро считается через BBP (как в #ifdef DEBUG части, challenge/src/server.cpp).
def pi_hex_digit(n):
    n -= 1
    def series(m):
        s = 0.0
        for k in range(n + 1):
            ak = 8 * k + m
            s = (s + pow(16, n - k, ak) / ak) % 1.0
        k = n + 1
        t = 1.0
        while True:
            ak = 8 * k + m
            t /= 16.0
            term = t / ak
            if term < 1e-17:
                break
            s = (s + term) % 1.0
            k += 1
        return s
    x = (4*series(1) - 2*series(4) - series(5) - series(6)) % 1.0
    return int(x * 16)

def get_byte(pos):
    return (pi_hex_digit(pos) << 4) | pi_hex_digit(pos + 1)

pi_bytes = [get_byte(p) for p in range(1_000_000, 1_002_048)]
flag = ''.join(chr(pi_bytes[o - 1_000_000]) for o in offsets)
print(flag)
  • Итоговый флаг: caplag{leibniz_david_bailey_peter_b0rwein_and_sim0n_pl0uffe_good_idea_guys_00}.