Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
vsock_poc — Расследование бага, лежащего в основе CVE-2021-26708 | Kitploit
Инструменты/GitHubGitHub/jordan9001/vsock_poc
Анализ уязвимостейЭксплуатацияОтладчикиСтатьи и ИсследованияОбучение и ОбразованиеЭксплуатация Бинарных Файлов
GitHubjordan9001/vsock_poc

vsock_poc

Расследование бага, лежащего в основе CVE-2021-26708

Репозиторий
2825 лет назадПроверено Kitploit

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

vsock_poc

Исследование ошибки, стоящей за CVE-2021-26708


В этом репозитории содержатся краткие заметки о CVE-2021-26708 и о том, как эту ошибку можно превратить в примитив записи типа Use After Free. Приведённый здесь PoC — это не полноценный эксплойт, а лишь мой тестовый стенд, который я использовал при исследовании этой ошибки. Он успешно использует объект из кэша kmalloc-64 после его освобождения, но в нём нет кода для груминга памяти и размещения чего-либо интересного в этом слоте.

Это интересная ошибка, о которой сообщил @a13xp0p0v. Она привлекла моё внимание тем, что патч был очень простым — он просто запрещает получение ссылки на vsk->transport вне блокировки в 5 различных местах. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c518adafa39f37858697ac9309c6cf1805581446

Ниже приведён краткий разбор процесса перехода от патча к примитиву use-after-free, который можно использовать для эксплуатации. Этот разбор должен быть полезен тем, кто хочет самостоятельно исследовать эту ошибку.

Подготовка окружения

Я скачал ядро linux 5.10.13 и вручную откатил патч, показанный выше. Дополнительную информацию о сборке и запуске ядра можно найти по следующей ссылке.

https://fedoraproject.org/wiki/Building_a_custom_kernel

Я также изменил параметры загрузки, чтобы включить отладку ядра через kgdb. Используя gdb с файлом vmlinux, который я собрал ранее, я получил все символы основного ядра, но не символы загружаемых модулей ядра. Код, связанный с уязвимостью, по умолчанию не загружался, но загружался в ядро при использовании семейства PF_VSOCK (в зависимости от того, как вы собрали ядро).

Чтобы получить символы загруженных модулей в kgdb, я как минимум один раз использовал vsock-сокет, а затем выполнил sudo cat /proc/modules | grep vsock, чтобы получить базовые адреса соответствующих модулей. В gdb я затем выполнял что-то вроде (gdb) add-symbol-file ./net/vmw_vsock/vsock.ko 0xffffffffc0567000, чтобы gdb знал, где в памяти находятся символы этого ko-файла. Наиболее важными были vsock.ko и vmw_vsock_virtio_transport_common.ko.

Охота за примитивом

Интересно работать в обратную сторону от патчей, потому что, в отличие от большинства поисков уязвимостей, вы точно знаете, что уже смотрите в правильное место. В данном случае из патча мы знаем, что ссылка на транспорт сохраняется до получения sock_lock. Можно с уверенностью предположить, что уязвимость связана с изменением транспорта, при том что используется старая ссылка.

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

Освобождение, часть 1

В af_vsock.c можно найти два места, где изменяется vsk->transport. Это vsock_assign_transport и vsock_deassign_transport. В vsock_assign_transport видно, что если уже существует другой транспорт, то перед установкой нового вызывается vsock_deassign_transport. Если посмотреть на возможные варианты вызова vsk->transport->destruct(vsk) здесь, то видно, что и loopback, и virtio-транспорты просто выполняют kfree параметра vsk->trans здесь. Бинго! Если мы сможем найти (1) путь к этому вызову, который можно совместить по времени с (2) уязвимой функцией, использующей ссылку на транспорт, полученную до его уничтожения, для доступа к vsk->trans, то у нас будет примитив.

Ищем путь к vsock_deassign_transport — он вызывается из vsock_sk_destruct или vsock_assign_transport. Функция vsock_sk_destruct установлена как sock->destruct, поэтому вызовы __sys_close или другие доступные вызовы на пути уничтожения, такие как sock_put, sock_close или vsock_release, могут привести сюда.

Наиболее релевантный путь к vsock_assign_transport проходит через vsock_stream_connect, но требует, чтобы сокет находился в нескольких конкретных состояниях, и приводит к вызову vsock_deassign_transport только в том случае, если транспорт должен измениться. При этом параметр vsk->trans будет заменён, только если новый транспорт не окажется NULL.

Использование

Прежде чем слишком углубляться в поиск подходящего для нас пути к освобождению, нужно убедиться, что существует корректный путь, использующий член vsk->trans с недействительной ссылкой на уничтоженный транспорт. Можно методично проверить каждое место использования транспорта с возможно недействительной ссылкой. Прослеживая эти дыры, можно найти те, где используется vsk->trans. Лучшим путём выглядит vsock_stream_setsockopt здесь, когда transport->notify_buffer_size записывает значение по смещению внутри vsk->trans для loopback и virtio-транспортов вот здесь. Если trans к этому моменту уже освобождён, мы получаем аккуратную запись u32 по смещению 0x28 в выделении из kmalloc-64.

Гонка

Использование vsock_stream_setsockopt в качестве примитива зависит от гонки между получением ссылки на транспорт и получением sock_lock. Это небольшое окно, и между этими точками много инструкций. Поэтому здесь можно использовать замечательную возможность linux под названием userfaultfd, чтобы повысить свои шансы. Этот механизм позволяет обрабатывать pagefault'ы в пользовательском режиме в удобное для нас время. См. https://man7.org/linux/man-pages/man2/ioctl_userfaultfd.2.html и https://man7.org/linux/man-pages/man2/userfaultfd.2.html.

С его помощью мы можем создать поток (гейтер), который получит sock_lock, а затем обратится к пользовательской памяти и вызовет pagefault. Мы можем держать этот поток приостановленным (с всё ещё удерживаемой блокировкой) сколько угодно долго. Другие потоки, пытающиеся получить эту блокировку, будут ждать, пока мы не отпустим гейтер и не снимем блокировку. Мы можем выстроить в очередь поток, который выполнит уничтожение, и поток, который будет использовать недействительную ссылку. Оба будут ждать на sock_lock, и теперь у нас отличный шанс выиграть гонку. Если следующим блокировку получит поток, выполняющий уничтожение, то наш вызов setsockopt завершится уже после этого. Он будет использовать указатель vsk->trans после того, как тот был освобождён (и заменён).

Если же первым выполнится вызов setsockopt, мы проиграем гонку, но можем безопасно повторить весь процесс.

Освобождение, часть 2

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

Между прочим, отладка этих путей может быть сложной; как вы можете себе представить, точка останова на системном вызове close будет срабатывать очень часто. Даже если использовать условные точки останова, чтобы останавливаться только в нужном потоке, машина будет работать крайне медленно. Интересный обходной путь — использовать ebpf с точками трассировки (tracepoints), которые будут вызывать bpf_trace_printk только при соблюдении условий. Затем точку останова kgdb можно поставить прямо на bpf_trace_printk, что приведёт нас к нужному месту. Это не работает с kprobes, потому что мы уже находимся в обработчике точки останова. Думаю, добавление вызова bpf_trace_kgdb_break в ebpf могло бы стать хорошим дополнением к ядру.

Когда я наконец переключился на путь vsock_assign_transport, всё собралось быстро. Чтобы выполнить требования, мы сначала подключаемся к VM_ADDR_CID_LOCAL, когда нет слушающего сервера. Это даст нам loopback-транспорт, но когда наше соединение истечёт по таймауту или завершится ошибкой, наше состояние вернётся к SS_UNCONNECTED. Это позволяет нам выполнить ещё одно подключение к адресу, превышающему VM_ADDR_CID_HOST, что приведёт к смене транспорта, уничтожению существующего транспорта и освобождению памяти. Важно делать это, когда на самом деле не зарегистрированы ни transport_g2h, ни transport_h2g, чтобы наш новый транспорт был NULL, а освобождённая ссылка осталась в vsk->trans.

Эксплуатация

Со всем этим, выстроенным в нужном порядке, мы получаем надёжный use-after-free, который можно использовать для повышения привилегий.

Этот репозиторий посвящён только достижению исходного use-after-free. Но теперь у нас есть примитив для записи значения по смещению в кэше kmalloc-64, где ранее был выделен virtio_vsock_sock. Я решил остановить разбор на этом месте, потому что... что, я должен делать здесь вообще всё?

Скачать инструмент