Files
Serdtse-Sysoly/qualifiers/biosignal-relay-pwn-hard/WRITEUP.md
T
2026-09-17 00:50:07 +03:00

3.7 KiB

BioSignal Relay

PWN hard

В раздатке есть ELF biosignal-relay с символами и без PIE. Сервис принимает CLP-файл, позволяет выбрать канал и запустить конвертацию. В бинаре есть обработчик flag_channel. Для получения флага нужно вызвать его при конвертации.

Решение

При анализе ELF находим структуру загруженного файла. Таблица каналов лежит сразу за 64-байтным буфером заголовка:

struct channel {
    uint32_t magic;
    uint32_t approved;
    uint64_t handler;
    char name[32];
}; // 48 байт

struct labfile {
    unsigned char header[64];
    struct channel ch[2];
};

При конвертации сервис проверяет magic == 0x43484e31, approved == 0x42494f21 и разрешает только два обработчика: audit_channel и flag_channel. Подходящая функция уже есть в списке разрешённых, поэтому достаточно подставить её адрес.

Смотрим загрузку. Основной заголовок копируется с ограничением, а вот дополнительный — по смещению hlen, которое приходит из самого файла:

memcpy(lab->header, buf, n < 64 ? n : 64);
size_t extra = n > 16 ? n - 16 : 0;
if (extra) memcpy(lab->header + hlen, buf + 16, extra);

Ставим hlen = 64, чтобы второй memcpy начал запись ровно с ch[0]. Если после первых 16 байт файла положить поддельный дескриптор, он займёт место настоящего канала.

Адрес flag_channel берём из readelf -sW public/biosignal-relay и сохраняем числом в переменной flag_channel. PIE выключен, поэтому адрес из таблицы символов подходит напрямую. Первые шесть байт файла должны быть ICLPCL, следующие два — hlen в little-endian. До дополнительного заголовка оставляем восемь нулевых байт, а с позиции 16 размещаем поддельный канал.

import struct

channel = struct.pack("<IIQ", 0x43484e31, 0x42494f21, flag_channel)
channel += b"motor-cortex".ljust(32, b"\x00")
payload = b"ICLPCL" + struct.pack("<H", 64) + b"\x00" * 8 + channel

Получается файл из 16 байт заголовка и 48 байт поддельного канала, всего 64 байта. В пункте 1) upload signal file на запрос file bytes: отправляем строку 64, затем на payload: передаём 64 байта файла без добавленного перевода строки. После этого выбираем 2) convert channel, канал 0. Поля magic и approved содержат нужные значения, а flag_channel входит в список разрешённых обработчиков. Проверки проходят, и сервис вызывает flag_channel, который выводит флаг.

Солвер.

Флаг

caplag{clp_header_extension_rewired_the_channel}