geektimes

Исследуем внутренние механизмы работы Hyper-V

  • вторник, 25 ноября 2014 г. в 02:10:54
http://habrahabr.ru/company/xakep/blog/242699/



Если бы работа хакера, а точнее программиста-исследователя происходила так, как это показано в классических фильмах: пришел, постучал по клавишам, на экране все замелькало зеленым, пароли взломались, а деньги внезапно переехали из пункта А в пункт В, — то жить было бы однозначно проще и веселее. Но в действительности любому серьезному хаку всегда предшествует основательная и скучная аналитическая работа. Вот ею мы и займемся, а результаты выкатим на твой суд в виде цикла из двух статей. Убедись, что у тебя есть достаточный запас пива и сигарет, — прочтение таких материалов опасно для неподготовленного мозга :).


Обнаружение бага, получившего впоследствии номер MS13-092 (ошибка в компоненте Hyper-V Windows Server 2012, позволяющая отправить гипервизор в BSOD из гостевой ОС или выполнить произвольный код в других гостевых ОС, запущенных на уязвимом хост-сервере), стало очень неприятным сюрпризом для инженеров Microsoft. До этого в течение почти трех лет никто не обнаруживал уязвимости в Hyper-V. До нее была только MS10-102, которую нашли в конце 2010 года. За эти четыре года популярность облачных сервисов сильно возросла, и исследователи проявляют все больший интерес к безопасности гипервизоров, лежащих в основе облачных систем. Однако количество публично доступных работ крайне невелико: исследователи неохотно тратят свое время на изучение таких сложных и плохо документированных архитектурных решений. В этой статье не рассказано о конкретных уязвимостях гипервизора, но она должна пролить свет на работу некоторых внутренних механизмов Hyper-V и тем самым частично упростить будущие исследования.

INFO



Перед прочтением статьи рекомендуется ознакомиться с отчетом ERNW, материалом «Hyper-V debugging for beginners», а также с официальным документом Hypervisor TLFS.


VMBus



Во время написания статьи в качестве Hyper-V-сервера и гостевой ОС использовалась Windows Server 2012 R2 Update 1 (тип машины — Generation 1), но для отражения некоторых особенностей работы шины были использованы и другие версии операционных систем Windows, что явно будет указано в статье. Тестовую среду лучше разворачивать в VMware Workstation 2014 July TechPreview или поздних, поскольку в более ранних версиях баг в Workstation не позволяет выполнять отладку виртуальных машин по сети (либо необходимо в конфигурации виртуальной машины принудительно указывать использование UEFI). Также в дальнейшем будет подразумеваться, что стенд развернут на аппаратной платформе Intel и функции гипервизора реализованы в hvix64.exe.

Термины и определения


  • Root-раздел (родительский раздел, root ОС) — Windows Server 2012 R2 с включенным компонентом Hyper-V;
  • Гостевая ОС — виртуальная машина Hyper-V с установленной Windows Server 2012 R2;
  • TLFS — Hypervisor Top-Level Functional Specification: Windows Server 2012 R2;
  • LIS — Linux Integration Services;
  • ACPI — Advanced Configuration and Power Interface



О Vmbus


Статья MSDN Hyper-V Architecture



Если говорить кратко, то VMBus — это технология взаимодействия гостевых операционных систем и root ОС. Соответственно, как в гостевой, так и в root ОС присутствуют компоненты, реализующие это взаимодействие через интерфейсы, предоставляемые гипервизором и описанные в TLFS 4.0. Microsoft разрабатывает гостевые компоненты для операционных систем семейства Linux, которые интегрированы в ядро Linux и выложены отдельно на GitHub: github.com/LIS/LIS3.5.

Начиная с Windows Server 2008 в ядре Windows появились функции, оптимизирующие работу операционной системы в виртуальной среде Hyper-V. Для сравнения, в ядре Windows Server 2008 (x64) реализовано всего 25 функций с префиксом Hvl, который идентифицирует их принадлежность к библиотеке интеграции с гипервизором, в Windows Server 2012 R2 уже присутствует 109 Hvl-функций.

Рассмотрим, каким же образом компоненты шины VMBus взаимодействуют с гипервизором, root ОС и гостевой ОС. Сперва заглянем в исходные коды LIS и увидим, что VMBus — это устройство, поддерживающее ACPI. ACPI позволяет стандартизировать аппаратную платформу для различных операционных систем и реализован в Hyper-V (как, впрочем, и в других популярных платформах виртуализации), что позволяет использовать стандартные утилиты для получения необходимой для исследования информации.

ACPI-устройства можно просмотреть при помощи утилиты ACPI tool, входящей в старую версию AIDA64 (в более поздних она была удалена). С ее помощью в _SB.PCI0.SBRG обнаруживаются два устройства: VMB8 и VMBS (см. рис. 1).


Рис. 1. Устройства VMB8 и VMBS

Сдампим ACPI DSDT (Differentiated System Description Table) таблицу, которая содержит информацию о периферийных устройствах и дополнительных функциях аппаратной платформы, с помощью той же утилиты ACPI Tool и декомпилируем AML-дизассемблером в ASL. Получим дамп, показанный на рис. 2.


Рис. 2. Дамп ASL

Поверхностное чтение Advanced Configuration and Power Interface Specification 5.0 дало понять, что в случае, если гостевая ОС — Windows 6.2 и выше, то будет задействовано устройство VMB8, в противном случае — VMBS. Единственное отличие этих устройств — объект _UID (Unique ID), который присутствует в VMB8. Если верить спецификации на ACPI, то присутствие этого объекта в таблице опционально и требуется только в том случае, если устройство не может иными способами предоставить операционной системе постоянный уникальный идентификатор устройства. Также стали известны ресурсы, которое использует устройство, — прерывания 5 и 7.

Для сравнения: в виртуальной машине типа Generation 2 присутствует только устройство VMBS, размещенное в _SB_.VMOD.VMBS (но с объектом _UID), которое использует только прерывание 5 (см. рис. 3).


Рис. 3. Часть ASL дампа Gen2

Обработка прерываний в виртуальной среде



В Windows обработку прерываний выполняют процедуры, зарегистрированные в таблице диспетчеризации прерываний (IDT). Между обнаруженными нами в ACPI DSDT IRQ 5 и 7 и обработчиками в IDT прямой связи нет, и для того, чтобы сопоставить прерыванию его обработчик, Windows использует арбитр прерываний (вообще, существует несколько классов арбитров, помимо IRQ, — DMA, I/O, memory).

WWW


Все об арбитрах в блоге MSDN

goo.gl/FuvG4R
goo.gl/V3UV8z
goo.gl/h1vXaf



Информацию о зарегистрированных арбитрах можно увидеть в WinDBG при помощи команды !acpiirqarb.

kd\> !acpiirqarb — Для гостевой Windows Server 2012 R2 Gen1 (рис. 4):


Рис. 4. !acpiirqarb Windows Server 2012 R2 Gen1 Guest

Вывод команды показывает, что для IRQ 7 адрес обработчика будет находиться в 0x71 элементе IDT, для IRQ 5 — в 0x81. Генерация номеров обработчиков прерываний происходит в функции acpi!ProcessorReserveIdtEntries на этапе построения дерева устройств PnP-менеджером, когда функциональный драйвер устройства еще не загружен. Регистрация ISR в IDT происходит уже на более поздних этапах, например при выполнении процедуры IoConnectInterrupt самим драйвером устройства. Однако, просмотрев элементы IDT, мы увидим, что ISR для прерываний 0x71 и 0x81 не зарегистрированы:

kd\> !idt -a

…………………………………………………………………………………………………………………………….

71: fffff80323f73938 nt!KxUnexpectedInterrupt0+0x388

81: fffff80323f739b8 nt!KxUnexpectedInterrupt0+0x408

…………………………………………………………………………………………………………………………….



В Windows Server 2012 R2 Gen2 для IRQ 5 был сопоставлен 0x90-й элемент IDT.

kd\> !acpiirqarb

    Processor 0 (0, 0):

    Device Object: 0000000000000000

    Current IDT Allocation:

    0000000000000000 - 0000000000000050 00000000 \<Not on bus\>
    A:0000000000000000 IRQ(GSIV):10

    0000000000000090 - 0000000000000090 D ffffe001f35eb520 (vmbus)
    A:ffffc00133972660 IRQ(GSIV):5

…………………………………………………………………………………………………………………………….


Однако, как показывает отладчик, ISR-процедура для вектора 0x90 также не определена:

  kd\> !idt -a

    90: fffff8014a3daa30 nt!KxUnexpectedInterrupt0+0x480



В Windows 8.1 x86 мы видим несколько иную картину:

kd\> !acpiirqarb

    Processor 0 (0, 0):

    Device Object: 00000000

    Current IDT Allocation:

    …………………………………………………………………………………………………………………………….

    0000000000000081 - 0000000000000081 D 87f2f030 (vmbus) A:881642a8
    IRQ(GSIV):fffffffe — такие значения обычно характерны для
    MSI-устройств.

    …………………………………………………………………………………………………………………………….

    00000000000000b2 - 00000000000000b2 S B 87f31030 (s3cap) A:8814b840
    IRQ(GSIV):5



При этом для прерывания с номером 0x81 определена ISR-процедура vmbus!XPartPncIsr:

kd\> !idt

    81: 81b18a0c vmbus!XPartPncIsr (KINTERRUPT 87b59e40 - (см. рис. 5))

    b2: 81b18c58 nt!KiUnexpectedInterrupt130



s3cap — вспомогательный драйвер для работы с эмулируемой Hyper-V видеокартой S3 Trio.


Vmbus interrupt object

Таким образом, ISR vmbus!XPartPncIsr регистрируется в IDT только в Windows 8.1 x86 (предположительно, в других x86 операционных системах, которые Microsoft поддерживает в качестве гостевых ОС для Hyper-V, используется такой же метод). Процедура vmbus!XPartPncIsr используется для обработки прерываний, генерируемых гипервизором.

В x64-битных системах, начиная с Windows 8\Windows Server 2012, интеграция с гипервизором реализована несколько иначе. В IDT операционных систем были добавлены обработчики системных прерываний, генерируемых гипервизором. Кратко рассмотрим, каким образом формируется IDT на этапе загрузки Windows.

После инициализации загрузчика Windows winload.efi IDT выглядит следующим образом (вывод скрипта на pykd в точке останова WinDBG в winload.efi при загрузке операционной системы с параметром /bootdebug):

   kd\> !py D:\\hyperv4\\idt\_winload\_parse.py

    isr 1 address = winload!BdTrap01

    isr 3 address = winload!BdTrap03

    isr d address = winload!BdTrap0d

    isr e address = winload!BdTrap0e

    isr 29 address = winload!BdTrap29

    isr 2c address = winload!BdTrap2c

    isr 2d address = winload!BdTrap2d


Затем во время выполнения winload!OslArchTransferToKernel IDT обнуляется, управление передается ядру Windows, где в функции nt!KiInitializeBootStructures IDT инициализируется значениями из таблицы KiInterruptInitTable:

  kd\> dps KiInterruptInitTable L40

    ……………………………………………………………………………………….

    fffff800\`1b9553c0 00000000\`00000030

    fffff800\`1b9553c8 fffff800\`1b377160 nt!KiHvInterrupt

    fffff800\`1b9553d0 00000000\`00000031

    fffff800\`1b9553d8 fffff800\`1b3774c0 nt!KiVmbusInterrupt0

    fffff800\`1b9553e0 00000000\`00000032

    fffff800\`1b9553e8 fffff800\`1b377810 nt!KiVmbusInterrupt1

    fffff800\`1b9553f0 00000000\`00000033

    fffff800\`1b9553f8 fffff800\`1b377b60 nt!KiVmbusInterrupt2

    fffff800\`1b955400 00000000\`00000034

    fffff800\`1b955408 fffff800\`1b377eb0 nt!KiVmbusInterrupt3

    ……………………………………………………………………………………….


Соответственно, обработчики системных прерываний 0x30–0x34 после завершения инициализации будут выглядеть следующим образом:

kd\> !idt

    ……………………………………………………………………………………….

    30: fffff8001b377160 nt!KiHvInterrupt

    31: fffff8001b3774c0 nt!KiVmbusInterrupt0

    32: fffff8001b377810 nt!KiVmbusInterrupt1

    33: fffff8001b377b60 nt!KiVmbusInterrupt2

    34: fffff8001b377eb0 nt!KiVmbusInterrupt3

    ……………………………………………………………………………………….


Виртуальную машину второго поколения в Hyper-V можно создать только на базе ОС, содержащих в ядре пять описанных выше дополнительных обработчиков. В целях генерации прерываний Intel представляет аппаратную функцию virtual interrupt delivery, однако Hyper-V не использует указанную возможность для передачи управления на эти обработчики. Вместо этого в гипервизоре происходит активация бита, соответствующего номеру вектора, в специальной области памяти с помощью инструкции вида lock bts [rcx+598h], rax, где rax — номер вектора прерывания (0x30–0x32), так что, возможно, разработчики Hyper-V сочли вариант с регистрацией процедуры vmbus!XPartPncIsr в качестве обработчика менее производительным решением, чем вариант генерации прерываний посредством виртуализации APIC на основе данных в виртуальных регистрах SINTx.

Указанные обработчики регистрируются в IDT даже в том случае, если операционная система работает вне среды Hyper-V. Каждый обработчик вызывает HvlRouteInterrupt, передавая индекс в качестве параметра (см. рис. 6).


Рис. 6. Дополнительные системные обработчики ядра Windows

HvlRouteInterrupt выглядит следующим образом (рис. 7).


Рис. 7. HvlRouteInterrupt

Эта функция вызывает обработчик из массива указателей HvlpInterruptCallback в зависимости от значения индекса. Этот массив в root ОС выглядит так:

   5: kd\> dps HvlpInterruptCallback 

    fffff802\`fff5cc30 fffff800\`dc639d50 winhvr!WinHvOnInterrupt

    fffff802\`fff5cc38 fffff800\`dd5a9ec0 vmbusr!XPartEnlightenedIsr

    fffff802\`fff5cc40 fffff800\`dd5a9ec0 vmbusr!XPartEnlightenedIsr

    fffff802\`fff5cc48 fffff800\`dd5a9ec0 vmbusr!XPartEnlightenedIsr

    fffff802\`fff5cc50 fffff800\`dd5a9ec0 vmbusr!XPartEnlightenedIsr

    fffff802\`fff5cc58 00000000\`00000000


XPartEnlightenedIsr по индексу, переданному из KiVmbusInterruptX, добавляет в DPC-очередь одну из двух возможных функций из массива DPC-структур в vmbusr: vmbusr!ParentInterruptDpc или же vmbusr!ParentRingInterruptDpc (рис. 8).


Рис. 8. DPC-объекты

Количество структур DPC в массиве определяется функцией vmbusr!XPartPncPostInterruptsEnabledParent и зависит от количества логических процессоров в root ОС. Для каждого логического процессора добавляется DPC c vmbusr!ParentInterruptDpc и vmbusr!ParentRingInterruptDpc. Функция vmbusr!ParentRingInterruptDpc определяет адрес DPC-процедуры для nt!KeInsertQueueDpc исходя из того, на каком процессоре выполняется в текущий момент.

В гостевой ОС VMBus регистрирует в массиве HvlpInterruptCallback только один обработчик:

 1: kd\> dps HvlpInterruptCallback 

    fffff803\`1d171c30 fffff800\`6d7c5714 winhv!WinHvOnInterrupt

    fffff803\`1d171c38 fffff800\`6d801360 vmbus!XPartEnlightenedIsr

    fffff803\`1d171c40 00000000\`00000000


Массив HvlpInterruptCallback заполняется с помощью экспортируемой ядром функции nt!HvlRegisterInterruptCallback. Обработчик WinHvOnInterrupt регистрируется при загрузке драйвера winhvr.sys (winhvr!WinHvpInitialize-> winhvr!WinHvReportPresentHypervisor-> winhvr!WinHvpConnectToHypervisor-> nt!HvlRegisterInterruptCallback)/

Остальные четыре обработчика регистрируются драйвером vmbusr.sys при его загрузке PnP-менеджером (vmbusr!RootDevicePrepareHardwareParent-> nt!HvlRegisterInterruptCallback).

Попробуем разобраться, каким же образом гипервизор передает управление на обработчики системных прерываний. Для этого необходимо обратиться к разделу Virtual Interrupt Control в TLFS. Если кратко, то Hyper-V управляет прерываниями в гостевой ОС через синтетический контроллер прерываний (SynIC), который является расширением виртуализованного локального APIC и использует дополнительный набор регистров, отображаемых на память (memory mapped registers). То есть каждый виртуальный процессор, помимо обычного APIC, обладает дополнительным SynIC. SynIC содержит две страницы: SIM (synthetic interrupt message) и SIEF(synthetic interrupt event flags). SIEF и SIM — это массивы из 16 элементов, размер элемента — 256 байт. Физические адреса (если быть точнее — GPA) этих массивов расположены в MSR-регистрах SIEF и SIMP соответственно. Адреса этих страниц для каждого логического процессора будут разными. Также для SynIC определены 16 регистров SINTx. Каждый из элементов массивов SIEF и SIM сопоставлен с соответствующим регистром SINTx. WinDBG показывает содержимое регистров SINTx с помощью команды !apic (начиная с WinDBG 6.3).


!apic в root ОС


!apic в гостевой ОС

Конфигурирование регистров SINT0 и SINT1 выполняется функцией nt!HvlEnlightenProcessor путем записи параметров в MSR-регистры 40000090h и 40000091h соответственно. SINT4 и SINT5 конфигурируются драйвером vmbusr.sys: vmbusr!XPartPncPostInterruptsEnabledParent-> winhvr!WinHvSetSint->winhvr!WinHvSetSintOnCurrentProcessor. SINT2 в гостевой ОС конфигурируется драйвером vmbus.sys в функции winhv!WinHvSetSintOnCurrentProcessor.

В каждом SINTx присутствует 8-битное поле Vector. От значения этого поля зависит то, какой процедуре обработки прерываний будет передано управление при выполнении гипервызовов, в параметрах которых задается PortID (HvSignalEvent, HvPostMessage).

SINTx может быть задан неявно (например, для сообщения перехвата всегда будет контролироваться регистром SINT0 и соответственно располагаться в первом элементе страница SIM), явно (для сообщений таймера) или же указан в параметрах порта, созданного с помощью гипервызова HvCreatePort. Одним из параметров является PortTypeInfo. Если тип порта — HvPortTypeMessage или HvPortTypeEvent, то в параметре PortTypeInfo присутствует TargetSint, содержащий номер SINT, к которому будет привязан порт и значение которого может быть в пределах от 1 до 15 (SINT0 зарезервирован для сообщений от гипервизора и не может быть указан в качестве TargetSint при создании порта).

Анализ значений активных регистров SINT в root ОС показывает, что в работе будут задействованы только три обработчика системных прерываний (KiHvInterrupt, KiVmbusInterrupt0, KiVmbusInterrupt1) из пяти. В каких целях в ядро были добавлены системные обработчики KiVmbusInterrupt2 и KiVmbusInterrupt3, обнаружить не удалось. Возможно, они будут нужны на серверах с большим количеством логических процессоров (например, 64), но, к сожалению, в тестовой среде это проверить не удалось. Также по значениям регистров SINTx видно, что обработчик nt!KiHvInterrupt (вектор 30) будет вызываться как при генерации прерываний от гипервизора, так и через порты, созданные с параметром TargetSint, равным 1.

Windows и TLFS



Для примера рассмотрим параметры портов, создаваемых при активации каждого из сервисов гостевых компонентов интеграции Hyper-V. На рис. 11 приведены характеристики портов, создаваемых для работы служб интеграции (по одному порту для каждого компонента).


Рис. 11. Порты служб интеграции

Взаимодействие root ОС и гостевой ОС в ходе работы компонентов Integration Services происходит через 5-й элемент массива SIEF, то есть обработчиком в root ОС будет KiVmbusInterrupt1.

Номер каждого следующего создаваемого порта равен предыдущему, увеличенному на 1. То есть если отключить все сервисы интеграции и затем включить их повторно, то номера портов, создаваемых для этих сервисов, будут находиться в диапазоне от 0x22 до 0x27.

Параметры порта можно увидеть, если подключиться отладчиком непосредственно к гипервизору и отслеживать данные, передаваемые обработчику гипервызова HvCreatePort, или же подключиться отладчиком к ядру и отслеживать параметры функции WinHvCreatePort в драйвере winhvr.sys.

Остальные порты, которые создаются при включении гостевой ОС (количество портов зависит от конфигурации гостевой операционной системы), представлены на рис. 12. Нумерация приведена в порядке создания портов при включении виртуальной машины Windows Server 2012 R2 с конфигурацией оборудования по умолчанию.


Рис. 12. Порты, создаваемые при запуске виртуальной машины00000000000000.

Важно отметить тот факт, что нулевой слот SIM как в гостевой, так и в родительской ОС зарезервирован для передачи сообщений от гипервизора. Формат таких сообщений документирован в TLFS. При передаче данных через оставшиеся слоты используется иной формат данных. Сообщения VMBus не документированы, но необходимая информация для работы с ними присутствует в исходных кодах LIS.

Некоторая информация об обработке VMBus-сообщений драйвером vmbusr.sys (см. рис. 13). Такие сообщения в root ОС обрабатывает функция vmbusr!ChReceiveChannelMessage, которая анализирует содержимое 4-го слота SIM и определяет код VMBus-сообщения. Если он равен 0 (CHANNELMSG_INVALID) или же больше 0x12, то функция возвращает ошибку и 0xC000000D (STATUS_INVALID_PARAMETER). В противном случае функция обрабатывает переданное гостевой или root ОС сообщение. Например, при включении компонента Guest Services root ОС отправляет гостевой ОС сообщение CHANNELMSG_OFFERCHANNEL, в ответ гостевая ОС отправляет сообщение CHANNELMSG_GPADL_HEADER, затем root ОС отправляет CHANNELMSG_GPADL_CREATED, получает обратно сообщение CHANNELMSG_OPENCHANNEL и в завершение диалога отправляет гостевой ОС сообщение CHANNELMSG_OPENCHANNEL_RESULT с кодом результата выполнения операции по созданию канала. Стоит обратить внимание на то, что перед обработкой каждого валидного сообщения функция ChReceiveChannelMessage выполняет проверку переданного сообщения (ChpValidateMessage), в частности на предмет того, кто является отправителем (root ОС или гостевая ОС), минимального размера тела сообщения. Для каждого типа сообщения заданы свои условия проверки. На рис. 13 отмечены те сообщения, которые будут обрабатываться в случае их отправки гостевой ОС (могут быть интересны, например, для создания фаззера).


Рис. 13. VMBus-сообщения, обрабатываемые драйвером vmbusr.sys


Для того чтобы понять, какими же сообщениями обмениваются root ОС и гостевая ОС, мы напишем драйвер, который заменяет адреса обработчиков из массива HvlpInterruptCallback в root ОС на свои собственные обработчики. Но об этом — в следующей статье.

Заключение



В первой части статьи были проанализированы изменения ядра операционной системы, внесенные Microsoft для оптимизации работы в виртуальной среде, влияющие на работу VMBus. В этом номере ][ мы разобрали теорию, а в следующем будет опубликована практическая часть исследования, поэтому запасайся терпением.


Впервые опубликовано в журнале «Хакер» от 11/2014.

Подпишись на «Хакер»