solutions

Решение задания BabyKernel с Midnight Sun CTF 2024 Quals

Краткий гайд: как подступиться к решению PWN на Windows Kernel


Пока в мировом CTF-сообществе происходит скандал, связанный с засланными казачками и воровством флагов у топовых команд, мы решили сделать небольшой позитивный вклад и рассказать, как подступиться к пывну ядра Windows на примере простого таска с прошедшего Midnight Sun CTF 2024 Quals.

Перед началом


Если вы совсем не знакомы с категорией PWN, то рекомендуем для начала ознакомиться с основами эксплуатации уязвимостей в пространстве пользователя. Вам могут помочь следующие ресурсы:


Если вы знакомы с данной категорией, но никогда не пытались эксплуатировать ядерные уязвимости, то для базового понимания общих концепций вы можете прочитать наш райтап на эксплуатацию ядра Linux — https://clck.ru/WjJxj

Настройка сетапа


Настроенный сетап — это всегда приятно. Если вы не занимаетесь отладкой ядра на повседневной основе, то наверняка у вас нет готового сетапа, и скорее всего, вы настраивали его пару раз или вообще никогда.

Готовый сетап увеличивает ваши шансы быстро решить таск, если это критично. Если нет, то вам придётся потратить некоторое время на соревновании на его подготовку.

Большого ума в настройке сетапа не нужно, он состоит из нескольких простых действий:

  • Установить чистую виртуальную машину с нужной версией Windows.
  • Включить отладку на виртуальной машине и прописать адрес подключения на машину, с которой вы будете отлаживать гостевую (там, где будет запущен windbg).
  • Загрузить символы для ядра и компонентов.
  • Настроить windbg на подключение.
  • Начать отладку.

Думаю, сложностей с установкой виртуальной машины возникнуть не должно. Как только вы установите ОС, вам нужно будет включить на ней отладку с помощью следующих команд:

bcdedit /debug on
bcdedit /dbgsettings net hostip:w.x.y.z port:n key:1.2.3.4

Вторая команда выставляет режим отладки (сетевая отладка), а также IP-адрес, порт и ключ для подключения. Вы должны указать машину, на которой у вас будет запущен windbg.

Проверить, всё ли корректно установилось, можно с помощью команды bcdedit /dbgsettings
В windbg можно нажать сочетание клавиш Ctrl+K и открыть настройки подключения. Прописываем порт и ключ:
Далее можем нажать Ctrl+Alt+K — что поставит брейкпоинт на начало загрузки, но это не обязательно.

Для удобной отладки необходимо добавить символы. Это делается один раз, и вы редко будете возвращаться к этому моменту.

В windbg открываем меню File -> SymbolFilePath и добавляем туда сервер с символами — это будет самый простой вариант для начала. Также нужно будет создать директорию, где у вас будет хранится кэш этих символов, туда же они и скачаются. В моём случае это выглядит так:

srv*D:\Symbols*http://msdl.microsoft.com/download/symbols;cache*D:\Symbols
В D:\Symbols хранятся pdb для различных модулей:
Если у вас что-то пошло не так, то можете обратиться к любому другому гайду по настройке отладки ядра Windows, благо в интернете их достаточно. В целом, это делается 1-2 раза, и после вы сможете делать это довольно быстро, — но лучше кратко записать для себя в заметку все действия.

Будем считать, что с сетапом мы разобрались.

Конечная цель ядерной эксплуатации и способы её достижения для Windows


Для многих CTF-пывнеров частым камнем преткновения становится непонимание того, куда надо прийти при ядерной эксплуатации. Здесь недостаточно просто вызвать system(“/bin/sh”) из текущего или другого процесса.

Нам надо вызвать эту или похожую команду с повышенными привилегиями. Чаще всего нам дают привилегии обычного пользователя, и мы можем запускать из-под него любой код, но он также будет с обычными привилегиями. Задача состоит в том, чтобы с помощью уязвимости в драйвере, который дан нам по заданию, поднять себе привилегии, запустить привилегированный шелл или сразу прочитать флаг.

Для того, чтобы получить такие привилегии в Windows, предусмотрено два базовых способа: вы можете украсть токен процесса SYSTEM (PID 4) и записать его в свой процесс или повысить привилегии у имеющегося токена для необходимого процесса.

Токен представляет собой некоторый дескриптор процесса, который в том числе отвечает за его привилегии. Поэтому заменяя свой токен токеном процесса с максимальными привилегиями, мы получаем эти самые привилегии.

Для того, чтобы украсть токен и записать к себе, необходимо иметь только одну уязвимость — AW (Arbitrary Write), с помощью которой можно записать с одного ядерного адреса в другой. Но это справедливо только для версий до Windows 10, где можно получить адрес SYSTEM-токена и своего токена через функцию NtQuerySystemInformation, а также, если запускаемый код находится в Medium Integrity Level. Иначе вам потребуется ещё и уязвимости для лика KASLR.

Резюмируя, можно сказать, что в самом простом варианте нам достаточно лишь AW для победы.

Пора взглянуть на таск.

Таск


Дано несколько файлов, среди них: драйвер с уязвимостью, ядро, запускатор, два вида клиентов, файл с описанием к таску, директория vagrant для генерации образа ВМ как на сервере для локальной проверки.
ExploitCTF – это мой готовый эксплоит для задания, он не был дан в изначальных файлах)
Откроем драйвер и немного изучим его.

Сразу могу сказать, что такая подача таска не очень красива со стороны организатора: дать драйвер без символьной информации в котором, по сути, всего две функции выполняют полезные действия, и сразу же дают примитивы. Как по мне, лучше бы был просто выдан исходник, потому что таск вообще не про поиск уязвимостей, а про базовую эксплуатацию для ядра Windows.

В целом, драйвер небольшой, и можно руками просмотреть все функции, и сразу заметить две интересные: sub_1400012E4 и sub_14000136C. В данных функциях вызываются ProbeForWrite и ProbeForRead соответственно. Эти функции нужны для проверки, что пользовательский буфер корректен, выровнен, и в него можно писать или читать.

После вызова этих функций идёт вызов некоторой функции — sub_140001D00.
Саму функцию я разбирать не стал, подумав о том, что так как это простой таск, то скорее всего нам просто сразу дали примитивы AW и AR. Я решил просто проверить это с помощью отладчика.
С помощью OSR Driver Load я загрузил драйвер и пошёл ставить в отладчике брейкпоинты на нужные мне места.
Для начала можно удостовериться, что наш драйвер был загружен:
Отлично, модуль загружен, можем ставить брейкпоинты на нужные нам функции и отлаживать драйвер. Однако мы совсем забыли показать, как взаимодействовать с драйвером, и сейчас это исправим.

Как взаимодействовать с драйвером


Чтобы взаимодействовать с драйвером, вам потребуется использовать некоторый WinApi, а для этого подойдет любой язык программирования, из которого это не сложно сделать. Мы будем использовать C++ (на самом деле, от C++ там ничего особо не будет) и Microsoft Visual Studio, чтобы собирать наш эксплоит.

Подключим необходимые библиотеки, а также нам понадобится некоторый набор недокументированных структур. Заголовочный файл, как и весь эксплоит, будет предоставлен ниже.
Теперь нам осталось понять, как подключаться к драйверу. Для этого мы будем использовать следующую функцию:
Для того, чтобы узнать имя драйвера, мы можем немного пореверсить, и найти его внутри бинаря:
Также можно просто взять название из вывода команды lm в windbg:
После подключения мы можем взаимодействовать с драйвером с помощью функции DeviceIoControl(), которая позволяет отправить в драйвер специальный код, а также какие-то данные. Для того, чтобы понять, какие коды мы можем отправлять, придётся опять немного вернуться к ревёрсу драйвера. Немного изучив работу драйвера, мы увидим, что он принимает всего два вида запросов от пользователя:
Теперь осталось разобраться с форматом данных. DeviceIoControl() принимает указатель на буфер, в котором могут быть переданы какие-то входные данные для драйвера. Для того, чтобы понять, где что передаётся, опять придётся немного поревёрсить:
Будем основываться на функции ProbeForWrite, которая принимает указатель на буфер для проверки, и его размер. Это поможет нам определить два поля входных данных. ProbeForWrite проверят буфер в пользовательском пространстве. Несложно понять, что оставшееся поле будет отведено под указатель на ядерный буфер, откуда будут писаться данные в наш буфер. В итоге получим следующее:
Теперь мы знаем, в каком формате передавать данные. В итоге мы можем посылать запросы в драйвер. Пример такого запроса:
Теперь нам надо подтвердить теорию, что мы просто имеем примитивы AR и AW, для этого мы можем взять из отладчика любой адрес и попытаться прочитать его в наш локальный буфер.

Для примера попробуем считать первые байты загруженного драйвера в память ядра.
Запишем этот адрес как адрес ядерного буфера для чтения.
Запустим наш эксплоит и проверим, что память читается:
Отлично, теперь мы можем приступить к написанию финального эксплоита.

Для этого нам понадобится узнать адрес токена для SYSTEM (PID 4) и для нашего процесса. В этом нам поможет отличная недокументированная функция NtQuerySystemInformation.

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

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

Если вы ничего не поняли, но вам очень интересно, то советую обратить внимание на следующие источники:

PWN