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

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

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

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

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

Категории

Все категории
Loading categories
CVE-2023-21768 — Эксплойт с доказательством концепции для CVE-2023-21768, уязвимость произвольной записи в ядро драйвера вспомогательных функций Windows (AFD.sys), позволяющая локальное повышение привилегий через кольцо ввода-вывода. | Kitploit
Инструменты/GitHubGitHub/h1bana/cve-2023-21768
Повышение привилегийАнализ уязвимостейЭксплуатацияЭксплуатация Бинарных Файлов
GitHubh1bana/cve-2023-21768

CVE-2023-21768

Эксплойт с доказательством концепции для CVE-2023-21768, уязвимость произвольной записи в ядро драйвера вспомогательных функций Windows (AFD.sys), позволяющая локальное повышение привилегий через кольцо ввода-вывода.

Репозиторий
33 лет назадЕщё не проверено

Популярное

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

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

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

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

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

CVE-2023-21768

Windows Ancillary Function Driver for WinSock

Согласно подробному описанию CVE-2023-21768, опубликованному Microsoft Security Response Center (MSRC), уязвимость существует в Ancillary Function Driver (AFD), файл которого в системе называется afd.sys. AFD модуль является kernel entry point для WinSock API. В этом анализе я буду использовать его для повышения привилегий в Windows 11.

Patch Diff and Root Cause Analysis

Скачиваем две версии afd.sys из Winbindex: одну – самую последнюю до исправления, и одну – после исправления. Затем используем Bindiff для сравнения этих версий. bindiff

Сравнивая две версии в целом, видим, что только одна функция имеет различия – AfdNotifyRemoveIoCompletion. Посмотрим подробнее на различия этой функции между двумя версиями. bindiff

Различий не так много. В версии после исправления добавлены инструкции ассемблера для установки параметров и вызова функции ProbeForWrite. Согласно документации Microsoft, эта функция проверяет, что адрес действительно принадлежит user-mode, имеет права на запись и правильно выровнен. Детальный анализ этого кода:

  • до исправления afd.sys version 10.0.22621.608 code1

  • после исправления afd.sys version 10.0.22621.1105 code2

Обе версии проверяют значение r15_1, если равно 0, записывают значение var_304 по указателю, заданному в поле struct_1. Если не равно 0, вызывается ProbeForWrite, чтобы убедиться, что указатель указывает на корректный адрес. В версии до исправления затем запись значения var_304 по указателю происходит без проверки. Из этого исправления можно предположить, что мы можем вызвать этот код с контролируемым значением arg3_1->field_18. Если можно установить адрес ядра в field_18, то мы сможем записать var_304 в область памяти ядра.

=> bug type: arbitrary kernel Write-Where

Теперь нужно найти способ вызвать баг. Функция AfdNotifyRemoveIoCompletion вызывается непосредственно в функции AfdNotifySock. crossRef

Аналогично, ищем перекрёстные ссылки на AfdNotifySock – она не вызывается напрямую ни из какой другой функции, но её адрес хранится по адресу в .rdata. cross2

Этот адрес находится непосредственно перед AfdIrpCallDispatch. cross3

Чтобы вызвать баг, я вызову DeviceIoControl с IOCTL_AFD_NOTIFY_SOCK, и будет вызвана AfdNotifySock.

root@kitploit:~
BOOL DeviceIoControl(
  [in]                HANDLE       hDevice,
  [in]                DWORD        dwIoControlCode,
  [in, optional]      LPVOID       lpInBuffer,
  [in]                DWORD        nInBufferSize,
  [out, optional]     LPVOID       lpOutBuffer,
  [in]                DWORD        nOutBufferSize,
  [out, optional]     LPDWORD      lpBytesReturned,
  [in, out, optional] LPOVERLAPPED lpOverlapped
);

reverse and debug

Для каждого драйвера в ядре создаётся объект DRIVER_OBJECT, который определяется так:

root@kitploit:~
typedef struct _DRIVER_OBJECT {
  CSHORT             Type;
  CSHORT             Size;
  PDEVICE_OBJECT     DeviceObject;
  ULONG              Flags;
  PVOID              DriverStart;
  ULONG              DriverSize;
  PVOID              DriverSection;
  PDRIVER_EXTENSION  DriverExtension;
  UNICODE_STRING     DriverName;
  PUNICODE_STRING    HardwareDatabase;
  PFAST_IO_DISPATCH  FastIoDispatch;
  PDRIVER_INITIALIZE DriverInit;
  PDRIVER_STARTIO    DriverStartIo;
  PDRIVER_UNLOAD     DriverUnload;
  PDRIVER_DISPATCH   MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT, *PDRIVER_OBJECT;

Последний элемент MajorFunction – это массив функций диспетчеризации драйвера для обработки взаимодействий между ядром и user-mode. Функция диспетчеризации, соответствующая вызову DeviceIoControl, хранится в MajorFunction[IRP_MJ_DEVICE_CONTROL].

root@kitploit:~
#define IRP_MJ_DEVICE_CONTROL           0x0e

Из функции DriverEntry afd.sys видно, что драйвер создаёт объект устройства "\Device\Afd": code3

Присваивает MajorFunction[IRP_MJ_DEVICE_CONTROL] = AfdDispatchDeviceControl, поэтому при вызове DeviceIoControl для взаимодействия с ядром будет вызвана эта функция. code4

В AFD есть две диспетчерские таблицы: AfdIrpCallDispatch и AfdImmediateCallDispatch. dispatchtable1 dispatchtable2

Легко заметить, что AfdDispatchDeviceIoControl вычисляет индекс по IoControlCode и берёт соответствующее значение из AfdIoctlTable для проверки по IoControlCode. 1

По расстоянию от начального адреса AfdImmediateCallDispatch до адреса хранения AfdNotifySock вычисляем индекс 73, что даёт управляющий код 0x12127. ioctl

root@kitploit:~
int main() {
    WSADATA WSAData;
    SOCKET s;
    SOCKADDR_IN sa;
    int ierr;

    WSAStartup(0x2, &WSAData);
    s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    memset(&sa, 0, sizeof(sa));
    sa.sin_port = htons(135);
    sa.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    sa.sin_family = AF_INET;
    ierr = connect(s, (const struct sockaddr*)&sa, sizeof(sa));

    char outBuf[100];
    DWORD bytesRet;
    DWORD inbuf1[100];

    memset(inbuf1, 0, sizeof(inbuf1));

    DeviceIoControl((HANDLE)s, 0x12127, (LPVOID)inbuf1, 0x30, outBuf, 0, &bytesRet, NULL);
    return 0;
}

it works!

bp1

Как было сказано в начале, уязвимость возникает, когда мы можем передать непроверенный указатель через структуру. Эта структура передаётся непосредственно из user-mode через lpInBuffer в DeviceIoControl. Затем она передаётся в AfdNotifySock как четвёртый параметр и в AfdNotifyRemoveIoCompletion как третий параметр.

para1 para2 para3

Поскольку структура неизвестна, я позволю IDA создать её автоматически. Теперь нужно найти способ передать данные в эту структуру и обойти необходимые проверки, чтобы добраться до уязвимого кода. Начнём с функции AfdNotifySock:

check1

Сначала размер структуры должен быть равен 0x30 байт.

check2

Ненулевые значения:

check3

Кроме того, при отладке я заметил, что происходит переход к сбою при предыдущей проверке UserBuffer, поэтому при вызове DeviceIoControl это значение устанавливается в NULL. После установки указанных значений я прошёл после проверки 2.

debug1 debug2

Следующая проверка, которую нужно обойти:

check4

ObReferenceObjectByHandle должна вернуть STATUS_SUCCESS, чтобы пройти эту проверку. То есть нужно передать корректный handle. Поискав, я не нашёл, как создать IoCompletionObjectType. Поэтому я последовал анализу https://securityintelligence.com/posts/patch-tuesday-exploit-wednesday-pwning-windows-ancillary-function-driver-winsock/. Использую функцию NtCreateIoCompletion для создания IoCompletionObjectType и передаю его handle в ObReferenceObjectByHandle. После обхода этой проверки поток программы переходит в цикл; в этом цикле нет места, где можно перейти к сбою, поэтому я просто устанавливаю значение dword20 в 0x1, чтобы выйти из цикла.

check5

После выхода из цикла программа вызывает AfdNotifyRemoveIoCompletion. Продолжим анализ функции AfdNotifyRemoveIoCompletion:

check6

Сначала программа проверяет другое поле структуры – оно должно быть ненулевым. Затем оно умножается на 0x20 и используется как параметр для вызова ProbeForWrite вместе с другим полем структуры. Здесь достаточно использовать адрес из user-mode с правами на запись и dwLen = 1. Последняя проверка перед тем, как мы сможем вызвать ошибку – значение, возвращаемое при вызове IoRemoveCompletion, должно быть STATUS_SUCCESS. После поиска я узнал, что функция NtRemoveIoCompletion при вызове обращается к IoRemoveCompletion. Согласно документации, функция NtRemoveIoCompletion является "ожидающим вызовом" и завершается, когда в указанном Io Completion Object появляется хотя бы одна запись о завершении. Запись добавляется при завершении операции I/O.

root@kitploit:~
NtRemoveIoCompletion(
  IN HANDLE               IoCompletionHandle,
  OUT PULONG              CompletionKey,
  OUT PULONG              CompletionValue,
  OUT PIO_STATUS_BLOCK    IoStatusBlock,
  IN PLARGE_INTEGER       Timeout OPTIONAL );

Также есть необязательный параметр Timeout, при достижении значения тайм-аута функция завершается. Однако установка timeout = 0 недостаточна для возврата функции – она вернёт код ошибки тайм-аута. Мы можем использовать функцию NtSetIoCompletion, чтобы увеличить счётчик ожидающих операций I/O в IoCompletionObjectType на 1 и завершить NtRemoveIoCompletion до истечения тайм-аута. После многократных попыток я заметил, что записываемое значение всегда равно 0x1.

exploit - LPE with IORING

Имея возможность записать значение 0x1 по адресу в kernel-mode, мы можем использовать эту ошибку для получения полного произвольного чтения/записи, используя I/O ring (новый механизм I/O, выпущенный Microsoft). Yarden Shafir написал очень подробный анализ этого метода, вы можете прочитать его здесь. Одна из операций, которую может выполнять приложение, — это выделить все буферы для будущих операций I/O, а затем зарегистрировать их в I/O ring. Предварительно зарегистрированные буферы ссылаются через объект I/O:

root@kitploit:~
typedef struct _IORING_OBJECT
{
    USHORT Type;
    USHORT Size;
    NT_IORING_INFO UserInfo;
    PVOID Section;
    PNT_IORING_SUBMISSION_QUEUE SubmissionQueue;
    PMDL CompletionQueueMdl;
    PNT_IORING_COMPLETION_QUEUE CompletionQueue;
    ULONG64 ViewSize;
    ULONG InSubmit;
    ULONG64 CompletionLock;
    ULONG64 SubmitCount;
    ULONG64 CompletionCount;
    ULONG64 CompletionWaitUntil;
    KEVENT CompletionEvent;
    UCHAR SignalCompletionEvent;
    PKEVENT CompletionUserEvent;
    ULONG RegBuffersCount;
    PVOID RegBuffers;
    ULONG RegFilesCount;
    PVOID* RegFiles;
} IORING_OBJECT, *PIORING_OBJECT;

Если уязвимость, например, рассмотренная в этой статье, позволяет обновлять/изменять поля RegBuffersCount и RegBuffers, то можно использовать стандартный API I/O ring для чтения и записи памяти ядра. Однако для использования функции NtQuerySystemInformation требуются права Medium IL. Для повышения с Low IL нужен способ утечки адреса ядра.

После того как IoRing->RegBuffers указывает на поддельный буфер, контролируемый пользователем, мы можем использовать обычные операции I/O ring для чтения и записи в любой адрес, указав индекс в поддельном буфере в качестве буфера:

  • Операция чтения + адрес ядра: ядро будет "читать" из выбранного нами файла в указанный адрес ядра, что приведёт к произвольной записи.
  • Операция записи + адрес ядра: ядро будет "записывать" данные из указанного адреса в выбранный нами файл, что приведёт к произвольному чтению.

Для более подробного понимания прочитайте анализ Yarden Shafir по ссылке выше.

issue

После попытки создать объект IO Ring и выполнить запись с помощью приведённого выше PoC-кода Windows вылетела после вызова DeviceIOControl /_ \ поэтому я использовал способ прямого вызова функций Ntfunction (˘・_・˘)

Affect range

  • Windows 11 21H1/22H2 до сборок 22000.1455/22621.1105
  • Windows Server 2022 до сборки 20348.1487

The patch

  • Исправление добавляет вызов ProbeForWrite
  • Версии исправления:
    • Windows 11 21H1: KB5022287 (OS Build 22000.1455)
    • Windows 11 22H2: KB5022303 (OS Build 22621.1105)
    • Windows Server 2022: KB5022291 (OS Build 20348.1487)

POC

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