Files
Serdtse-Sysoly/finals/zcrx-broker-pwn-hard/WRITEUP.md
T
2026-09-17 00:50:07 +03:00

3.5 KiB
Raw Blame History

ZCRX Broker

PWN hard

ZCRX Broker управляет буферами приёма без копирования. В раздатке есть ELF zcrx-broker без PIE. У сервиса собственный пул памяти. Два отложенных события могут вернуть один блок в список свободных дважды. Добиваемся выделения одного адреса двум объектам. Тогда запись в новый blob изменит поля действующего объекта прав.

Решение

Создаём niov view через пункт 1, label любой. Новый view имеет refs = 1. Для view 0 дважды вызываем 2) arm lifecycle event: сначала путь scrub, затем refill. Оба события запоминают одно и то же состояние с одной ссылкой.

В обоих снимках счётчик ссылок равен единице. Поэтому при выборе 3) drain lifecycle events сервис обрабатывает оба события и дважды возвращает один и тот же блок в пул. В списке свободных теперь две записи с одним адресом — double free в самописном аллокаторе.

Выделяем capability через пункт 4: она занимает первую запись. Вторую пока оставляем в пуле. У capability есть закодированный обработчик, который можно прочитать через 5) inspect capability, cap 0. Значение encoded_handler сохраняем числом в encoded_audit.

Обработчик по умолчанию — audit_handler, кодирование — XOR с pointer guard. Адреса функций берём из readelf -sW public/zcrx-broker, поскольку PIE выключен. Восстанавливаем guard и кодируем адрес экспорта:

ptr_guard = encoded_audit ^ audit_handler
encoded_export = export_handler ^ ptr_guard

Пункт 6) allocate blob заберёт вторую запись того же блока. Blob и живая capability окажутся в одной памяти. Сначала готовим 176-байтную подделку.

import struct

payload = struct.pack("<QQQ", 0x4341505245433031, encoded_export, 0x4558504f52542121)
payload += b"/broker/export\x00".ljust(48, b"\x00")
payload = payload.ljust(176, b"\x00")

Выбираем 6) allocate blob, на запрос blob bytes: отправляем строку 176, затем на blob payload: передаём payload без добавленного перевода строки. Его поля задают magic, encoded_handler, rights и нуль-терминированный путь. Пункт 7) commit capability, cap 0, декодирует обработчик тем же guard. Проверки проходят, управление уходит в export_handler, и сервис печатает флаг.

Солвер.

Флаг

caplag{double_returned_niov_shared_the_capability}