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

Статья Эксплуатация уязвимостей Windows с помощью Hyper-V: швейцарский армейский нож хакера

yashechka

Генератор контента.Фанат Ильфака и Рикардо Нарвахи
Эксперт
Регистрация
24.11.2012
Сообщения
2 344
Реакции
3 563
Отказ от ответственности

Я создал этот пост исключительно в образовательных целях, и этот пост в блоге ограничен по глубине внутренним устройством Hyper-V.

Введение

Hyper-V — это технология виртуализации Microsoft для операционных систем Windows, обеспечивающая уровень виртуализации, который позволяет нескольким виртуальным машинам работать на одной физической машине. Хотя он обеспечивает безопасную и изолированную среду для запуска виртуальных машин, реализация Hyper-V для Windows также представляет значительный вектор атаки, который не совсем известен многим специалистам по безопасности и совместим с технологиями безопасности, развернутыми Windows, такими как KPP. (PatchGuard) и VBS (безопасность на основе виртуализации), если они загружены до их полной инициализации.

Анализ технологии Hyper-V

Hyper-V, разработанная Microsoft, представляет собой широко используемую технологию, которая запускается до полной загрузки операционной системы и паравиртуализирует различные компоненты ядра; его задача также состоит в том, чтобы установить барьер между собой и другими компонентами, используя либо Intel VMX, либо AMD SVM, и предоставить гостю возможность выполнять определенные задачи, проходя через уровень виртуализации. Гости, осведомленные о том, что они присутствуют в Hyper-V, могут использовать многие функции, известные хосту как «Enlightenment».

Связь между системой, поддерживающей гипервизор, и Hyper-V является важной частью процесса виртуализации, и этот процесс прост, когда Hyper-V использует гипервызовы для связи между гостем и хостом. Эти гипервызовы реализуются с помощью инструкции «vmcall/vmmcall» в системах на базе x86, которая запускает vmexit, который гипервизор может перехватить и на который отреагирует соответствующим образом.

Внутреннюю работу Hyper-V можно проанализировать, проанализировав модули для Hyper-V, присутствующие в корневом каталоге системы, где он хранится как и hvix64.exe для hvax64.exe процессоров Intel и AMD соответственно.

1685625965506.png


Информация о гипервызове, используемая для связи, хранится в разделе CONST двоичного файла. Когда мы пытаемся его проанализировать, мы встречаемся с чем-то вроде этого.

1685625974636.png


Он следует определенному шаблону, который мы можем попытаться проанализировать и понять, сверив его с документацией по интерфейсу гипервызова, предоставленной Microsoft здесь (https://learn.microsoft.com/en-us/v...ows/tlfs/hypercall-interface#hypercall-inputs) и ntoskrnl.exe, которая является ядром Windows.

Окончательная структура выглядит примерно так:

1685625985768.png


1685625997097.png


Хотя гипервызовы модуля Hyper-V не являются общедоступными в документации Microsoft, мы все же можем глубже изучить их функциональность. Обратное обращение всех 239 гипервызовов было бы сложной задачей, и определенно выходит за рамки этого сообщения в блоге. Однако, изучив экспортированные функции ntoskrnl.exe, такие как HvlInvokeFastExtendedHypercall и HvcallInitiateHypercall, мы можем лучше понять, что делает каждый гипервызов, следуя потоку управления каждого из обработчиков, на которые он ссылается, где он может дать некоторое представление, если не полное описание их назначения с помощью символов. Чтобы быть точным, следует отметить, что за гипервызов к Hyper-V отвечает несколько других драйверов, таких как winhvr.sysи securekernel.exe.

Теперь, когда мы знаем, как Hyper-V справляется с гипервызовами, давайте углубимся в то, как ядро Windows обрабатывает Hyper-V, когда оно знает, что система виртуализируется под ним.

Поток управления Hyper-V в ядре Windows

Граф потока управления запуском ядра Windows выглядит примерно так:

1685626016428.png


Связанные с Hyper-V функции в ядре Windows обычно имеют префикс Hvl, а инициализация Hyper-V разделена на три этапа, каждый из которых выполняется на разных этапах процесса инициализации ядра. Фаза инициализации, которая нас больше всего интересует, — первая, HvlPhase0Initialize. На этом этапе настраивается интерфейс гипервызова, и гипервызов выполняет обратные вызовы обоих критических компонентов, позволяя операционной системе взаимодействовать с гипервизором.

Чтобы захватить Hyper-V, нам нужно настроить несколько вещей, которые инициализируются на первом этапе фазы enlightenment.

C:
NTSTATUS HvlpTryConfigureInterface( _LOADER_PARAMETER_BLOCK* LoaderBlock )
{
    HviGetHypervisorFeatures( &HypervisorFeatures );
    if ( !HviIsHypervisorMicrosoftCompatible( ) || !HypervisorFeatures.PartitionPrivileges.AccessHypercallMsrs )
    {
        HvlpHypercallCodeVa = 0;
        return ERROR_HV_NOT_PRESENT;
    }

    if ( LoaderBlock )
    {
        Extension = LoaderBlock->Extension;
        MappedPhysPage = Extension->HypercallCodeVa;
        SecureKernelRunning = Extension->IumEnabled != 0;
    }
    else
    {
        MappedPhysPage = 0;
        SecureKernelRunning = 0;
    }

    if ( MappedPhysPage )
        goto $SetupHypercallPtrs;

    __writemsr( HV_X64_MSR_GUEST_OS_ID, uint16_t(NtBuildNumber) | ((*(uint8_t*)&CmNtCSDVersion | 0x1040A0000) << 16) );
    HypercallPhysicalPage = __readmsr( HV_X64_MSR_HYPERCALL ) | 1;

    if ( HyperVisorFeatures.PartitionPrivileges.AccessMemoryPool || SecureKernelRunning )
    {
        AllocatedPhysPage.QuadPart = HypercallPhysicalPage & -0x1000;
        MappedPhysPage = HalpMapEarlyPages( AllocatedPhysPage, 1, PAGE_EXECUTE_READ, SecureKernelRunning );
        if ( MappedPhysPage )
            goto $SetupHypercallPtrsAndHypercallMSR;
    }
    else
    {
        if ( !LoaderBlock )
        {
            PhysicalAddress = MmGetPhysicalAddress( HvlpHypercallCodeVa );
            MappedPhysPage = HvlpHypercallCodeVa;
            AllocatedPhysPage = PhysicalAddress;
            goto $SetupHypercallPtrsAndHypercallMSRAndSetHypercallPhysicalPage;
        }

        MappedPhysPage = HalpAllocateEarlyPages( LoaderBlock, MEMORY_CACHING_TYPE::MmCached, &AllocatedPhysPage, PAGE_EXECUTE_READ );
        if ( MappedPhysPage )
        {
$SetupHypercallPtrsAndHypercallMSRAndSetHypercallPhysicalPage:
            HypercallPhysicalPage = AllocatedPhysPage.QuadPart ^ (LOWORD( AllocatedPhysPage.LowPart ) ^ (unsigned __int16)HypercallPhysicalPage) & 0xFFF;

$SetupHypercallPtrsAndHypercallMSR:
            __writemsr( HV_X64_MSR_HYPERCALL, HypercallPhysicalPage );

$SetupHypercallPtrs:
            HvcallCodeVa = MappedPhysPage;
            _InterlockedExchange64( &HvlpHypercallCodeVa, MappedPhysPage );
            return STATUS_SUCCESS;
        }
    }

    return STATUS_INSUFFICIENT_RESOURCES;
}

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

C:
NTSTATUS InitializeHypercallPageForGuest(QWORD FContext)
{
  Status = 0;
  Idx = 0;
  HypercallPhysicalPage = 0;

  VmcallStubAddress = VmcallRetStub;
  Size = (unsigned int)VmcallRetStub0 - (unsigned int)VmcallRetStub1;

  if ( *(int*)(FContext + 0x10B04) < 0x501 )
    Size = (unsigned int)&VmcallRetStub1Align - (unsigned int)VmcallRetStub1;

  if ( *(int*)(FContext + 0x10B04) < 0x501 )
    VmcallStubAddress = VmcallRetStub1;

  while ( 1 )
  {
    v6 = *(QWORD*)(FContext + 0x120);
    v7 = (v6 & 0x20000) != 0 ? 3 : ((v6 & 0x10000) != 0) + 1;
    if ( Idx >= v7 )
      break;

    Status = TranslateGuestToHostPA(*(QWORD *)(FContext + 304), (DWORD *)(FContext + 8816), &HypercallPhysicalPage);
    if ( Status )
    {
      sub_FFFFF800002B0564(FContext);
      return Status;
    }

    HypercallPagePfn = HypercallPhysicalPage >> 12;
    *(QWORD*)(FContext + 0x46C0 * Idx + 0x3770) = HypercallPhysicalPage >> 12;

    MappedPage = MapHostPA(NtCurrentTeb()->NtTib.ExceptionList, HypercallPagePfn, 6);
   
    // Copy the vmcall and return stub to the mapped page.
    memcpy((void *)MappedPage, VmcallStubAddress, (unsigned int)Size);

    // Basic sanity check.
    if ( Size != 4096 )
      memset((void *)(Size + MappedPage), 0x90, 4096 - Size); // Setting the rest of the page with nops.
    UnmapHostVA(NtCurrentTeb()->NtTib.ExceptionList, MappedPage, 0);

    ++Idx;
  }

  return Status;
}

Второй функцией, которая потребуется для инициализации интерфейса Hyper-V, будет HvlpSetupBootProcessorEarlyHypercallPages

C:
NTSTATUS HvlpSetupBootProcessorEarlyHypercallPages( _LOADER_PARAMETER_BLOCK* LoaderBlock )
{
    // Allocate 6 RW pages.
    VirtualAddress = HalpAllocateEarlyPages( LoaderBlock, 6, &PhysicalPage, PAGE_READWRITE );
    if ( !VirtualAddress )
        return STATUS_INSUFFICIENT_RESOURCES;

    KeGetCurrentPrcb()->HypercallCachedPages = VirtualAddress;

    for ( int i = 0; i < 2; i++ )
        *(uint64_t*)(VirtualAddress + 16 + i * 0x1000) = uint64_t(PhysicalPage.QuadPart) + i * 0x1000;

    return STATUS_SUCCESS;
}

Эта функция выделяет доступную для записи страницу и инициализирует поле HypercallCachedPages в текущем блоке процессора. Затем он инициализирует структуру кэшированной страницы, это важно, поскольку некоторые гипервызовы пытаются заблокировать страницу для безопасности потоков, вызывая заранее HvlpAcquireHypercallPage и, следовательно, получая доступ к HypercallCachedPages указателю, который указывал бы на нулевой указатель, если Hyper-V не инициализирован и не подключен к гостю и в конечном итоге проверит систему на наличие ошибок из-за ошибки страницы. Следовательно, у нас нет другого выбора, кроме как инициализировать поле в структуре блока процессора.

Решить эту проблему относительно просто, поскольку мы можем скопировать то, что должна делать исходная функция.

C:
/*
*    IPI callback for setting the HypercallCachedPages pointer in the KPRCB.
*/
ULONG_PTR SetHypercallCachedPagesIPICallback( _In_ ULONG_PTR CachedPagePtr )
{
    uint64_t* HypercallCachedPages = (uint64_t*)(uint64_t( KeGetPcr( )->CurrentPrcb ) + GetHypercallCachedPagesOffset( ));
    *HypercallCachedPages = CachedPagePtr;
    return 0;
}

// ... Omitted code ...

// Check if Hyper-V is NOT running.
if (!HyperVRunning)
{
    // When Hyper-V is off, HypercallCachedPages is null, and this is
    // accessed in multiple places and will cause a page fault if not initialized.
    HypercallCachedPages = MmAllocateContiguousMemory( 0x6000, PHYSICAL_ADDRESS{ .QuadPart = -1 } );
    if (!HypercallCachedPages)
        return false;

    // Zero out the newly allocated memory.
    memset( HypercallCachedPages, 0, 0x6000 );

    int64_t HypercallCachedPagesPhys = MmGetPhysicalAddress( HypercallCachedPages ).QuadPart;

    for (int i = 0; i < 2; i++)
        *(uint64_t*)(uint64_t( HypercallCachedPages ) + 16 + i * 0x1000) = HypercallCachedPagesPhys + i * 0x1000;

    // Do an IPI on all cores to set HypercallCachedPages for every core’s processor block.
    KeIpiGenericCall( SetHypercallCachedPagesIPICallback, ULONG_PTR( HypercallCachedPages ) );
}

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

HvlpDetermineEnlightenments запрашивает возможности, предоставляемые Hyper-V, и устанавливает соответствующие флаги, HvlpRootFlagsа HvlpFlags также устанавливает определенные вещи, такие как флаги просветления. Тем не менее, наиболее важным аспектом этой функции является следующее:

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

1685626088307.png


1685626099499.png


Приведенный выше код устанавливает для различных записей структуры, предоставленной в качестве первого параметра, несколько значений, таких как просветления, адреса функций и т. д. На первый взгляд структура может показаться сложной для интерпретации, но, к счастью, структура, передаваемая в функцию, называется HAL_INTEL_ENLIGHTENMENT_INFORMATION. Установив тип указателя на эту структуру, мы можем легко понять значение каждой записи.

1685626114146.png


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

1685626127732.png


Глобальная переменная HalpEnlightenment следует структуре HAL_INTEL_ENLIGHTENMENT_INFORMATION. Однако из-за отсутствия информации в PDB IDA не смогла понять всю структуру. Необходимо помнить об этом ограничении при анализе бинарника. Кроме того, большинство полей в этой глобальной структуре не инициализированы, поскольку Hyper-V не подключен и не инициализирован для гостя. Поэтому нам нужно инициализировать соответствующие записи. Реализовать каждую функцию в этом списке может быть утомительной задачей. К счастью, Microsoft сделала большинство функций-членов в структуре необязательными. В результате мы можем инициализировать только те записи, которые хотим перехватить, а не все записи.

Вернемся к эксплуатации!

Поскольку теперь мы знаем основной маршрут инициализации Hyper-V, мы можем изучить, как мы можем злоупотреблять полученной информацией, чтобы получить контроль над операционной системой.

Во-первых, нам нужно выяснить, как перехватывать эти гипервызовы. И, как обсуждалось в предыдущем разделе, HvcallInitiateHypercallи HvcallFastExtendedотвечают за обработку гипервызовов, которые оба вызывают HvcallCodeVa, что указывает на a vmcall/vmmcall, как упоминалось в предыдущем разделе поста. Перехватить это было бы легко, так как можно перезаписать указатель заглушки гипервызова без каких-либо помех, таких как VBS или KPP (Patchguard). Во-вторых, нам нужно настроить Enlightenments, чтобы ОС знала, что Hyper-V предоставляет xвозможности для гостя.

Теперь проблема заключается в том, что когда Hyper-V не работает, а ОС не ""просветлена"", ОС перекладывает тяжелую работу на Hyper-V, а не делает все сама, поскольку это лишило бы цели «просветления». Ну, это именно та проблема, с которой мы столкнемся, когда применим это к реальной машине, которая не ""просветлена"", и мы должны эмулировать то, что ОС ожидает от гипервизора. Например, посмотрите на переключатели адресного пространства в функции SwapContext, отвечающей за переключение адресных пространств.

1685626144564.png


В коде выше показана часть, отвечающая за переключение контекстов процесса, и есть проверка, доступно ли ""просветление"" переключения адресного пространства; если это так, он ожидает, что Hyper-V обработает переключатель CR3 и сброс TLB.

Тем не менее, приятно то, что большинство просветлений гипервызовов уже имеют реализацию этой функции в ядре, но используют Hyper-V как средство сделать что-то быстрее или лучше. Таким образом, мы можем перепроектировать функцию и повторно реализовать ее в нашем коде или вернуться к коду, ответственному за нее.

Еще одним недостатком является то, что множество функций инициализируется через HalpHvInitDiscard, которые будут работать только в том случае, если Hyper-V был инициализирован при загрузке. Следовательно, нам потребуется настроить их, если нам нужно перехватить указанную функцию; теперь мы можем добиться этого с помощью сканирования шаблона структуры по ее многочисленным ссылкам. Одной из особенностей, которая страдает от этой проблемы, является виртуализированное состояние сна, HalpHvEnterSleepState которое может инициировать гипервызов только тогда, когда указатель функции инициализируется с помощью HvlEnterSleepState.

1685626157610.png


Идя дальше, это по-прежнему позволяет нам перехватывать довольно много вещей, таких как спин-блокировки, где мы можем перехватывать еще больше вещей, проверяя определенные вызовы, используя адрес возврата в стеке. Например, мы можем взять SwapContextфункцию, в которой функция HvlNotifyLongSpinWait вызывается, как подсказку для виртуализированного планировщика, чтобы отложить следующую инструкцию для экономии производительности. Теперь функция HvlNotifyLongSpinWait представляет собой гипервызов к Hyper-V, который можно перехватить вышеописанным способом.

1685626166580.png


Заключительные заметки

Следует отметить, что HvcallCodeVa ИС защищена КПП (Patch Guard), поэтому следует отметить, что этот проект должен быть либо загружен до полной инициализации patchguard.

Источник

Это не было бы полным постом в блоге без легко вставленного кода, не так ли?

HyperDeceit — это универсальная библиотека, которая включает в себя простой в использовании интерфейс для перехвата гипервызовов. HyperDeceit также включает в себя базовый эмулятор, который позволяет ему работать в системах, где Hyper-V отключен.

Yumekage — это демо-проверка концепции, в которой HyperDeceit используется для перехвата контекстных свопов для создания скрытых областей памяти для процесса пользовательского режима.

Отдельное спасибо
Daax за обратную связь и исправление некоторых ошибок в публикации.
AVX за отзыв.

Переведено специально для xss.pro
Автор перевода: yashechka
Источник: https://reversing.info/posts/hyperdeceit/
 


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