Досліджуємо внутрішні механізми роботи Hyper-V: Частина 2



З часу публікації першої частини статті глобально в світі нічого не змінилося: Земля не наскочила на небесну вісь, все так само зростає популярність хмарних сервісів, так само гипервизоре компанії Microsoft не були виявлені нові дірки, а дослідники не хочуть витрачати свій час на пошук багів в погано документованої і мало вивченою технології. Тому я пропоную тобі освіжити пам'ять першою частиною з попереднього номера, поповнити запас свого бару і приступити до читання, адже сьогодні ми зробимо драйвер, який взаємодіє з інтерфейсом гіпервізора і відслідковує передаються гіпервізором повідомлення, а також вивчимо роботу компонентів служб інтеграції Data Exchange.


Обробка повідомлень гіпервізора

dvd.xakep.uk ми виклали драйвер, написаний за допомогою Visual Studio 2013. Він повинен бути завантажений в root ОС, наприклад з допомогою OSRLoader. Для відправки IOCTL-кодів використовується проста програма SendIOCTL.exe. Після відправки IOCTL-коду INTERRUPT_CODE драйвер починає виконувати обробку даних, переданих гіпервізором через нульовий слот SIM. На жаль, мінлива HvlpInterruptCallback, в якій міститься адреса масиву з покажчиками обробників повідомлень, ядром не експортується, тому для її виявлення необхідно проаналізувати код експортованої ядром функції HvlRegisterInterruptCallback, містить необхідний нам адресу масиву. Також, на жаль, не вийде просто викликати HvlRegisterInterruptCallback для реєстрації свого обробника повідомлень, так як в самому початку функції йде перевірка значення змінної HvlpFlags. Якщо змінна дорівнює 1 (а їй присвоюється значення на початкових етапах завантаження ядра), то функція припиняє виконання, повертає код помилки 0xC00000BB (STATUS_NOT_SUPPORTED) і, відповідно, реєстрація обробника не відбувається, тому для заміни обробників доведеться написати свій варіант функції HvlpInterruptCallback. В драйвері hyperv4 необхідні дії виконуються функцією RegisterInterrupt:

int RegisterInterrupt()
{
UNICODE\_STRING uniName;
PVOID pvHvlRegisterAddress = NULL;
PHYSICAL\_ADDRESS pAdr = {0};
ULONG i,ProcessorCount;
// Отримуємо число активних ядер процесорів
ProcessorCount = KeQueryActiveProcessorCount(NULL);
// Виконуємо пошук адреси експортованої функції HvlRegisterInterruptCallback
DbgLog("Active processor count",ProcessorCount);
RtlInitUnicodeString(&uniName,L"HvlRegisterInterruptCallback");
pvHvlRegisterAddress = MmGetSystemRoutineAddress(&uniName);
if (pvHvlRegisterAddress == NULL){
DbgPrintString("Cannot find HvlRegisterInterruptCallback!");
return 0;
}
DbgLog16("HvlRegisterInterruptCallback address ",pvHvlRegisterAddress);
// Виконуємо пошук адреси змінної HvlpInterruptCallback, FindHvlpInterruptCallback((unsigned char *)pvHvlRegisterAddress);
// Робимо заміну оригінальних обробників на свої
ArchmHvlRegisterInterruptCallback((uintptr\_t)&ArchmWinHvOnInterrupt,(uintptr\_t)pvHvlpInterruptCallbackOrig,WIN\_HV\_ON\_INTERRUP\_INDEX);
ArchmHvlRegisterInterruptCallback((uintptr\_t)&ArchXPartEnlightenedIsr,(uintptr\_t)pvHvlpInterruptCallbackOrig,XPART\_ENLIGHTENED\_ISR0\_INDEX);
ArchmHvlRegisterInterruptCallback((uintptr\_t)&ArchXPartEnlightenedIsr,(uintptr\_t)pvHvlpInterruptCallbackOrig,XPART\_ENLIGHTENED\_ISR1\_INDEX);
ArchmHvlRegisterInterruptCallback((uintptr\_t)&ArchXPartEnlightenedIsr,(uintptr\_t)pvHvlpInterruptCallbackOrig,XPART\_ENLIGHTENED\_ISR2\_INDEX);
ArchmHvlRegisterInterruptCallback((uintptr\_t)&ArchXPartEnlightenedIsr,(uintptr\_t)pvHvlpInterruptCallbackOrig,XPART\_ENLIGHTENED\_ISR3\_INDEX);
// Так як значення SIMP для всіх ядер різне, то необхідно отримати фізичні адреси всіх SIM



WARNING
Під час експериментів, пов'язаних з інтенсивною роботою віртуальних машин, краще замінювати один обробник у масиві HvlpInterruptCallback, оскільки заміна всіх відразу призводить до нестабільної роботи системи (принаймні, при великому потоці налагоджувальних повідомлень через KdPrint і WPP).



// зробити можливим доступ до вмісту сторінки, смапировав її з допомогою Mm MapIoSpace, і зберегти отримані віртуальні адреси кожної сторінки в масив для подальшого використання
for (i = 0; i < ProcessorCount; i++)
{KeSetSystemAffinityThreadEx(1i64 << i);
DbgLog("Current processor number",KeGetCurrentProcessorNumberEx(NULL));
pAdr.QuadPart = ArchReadMsr (HV\_X64\_MSR\_SIMP) & 0xFFFFFFFFFFFFF000;
pvSIMP[i] = MmMapIoSpace (pAdr, PAGE\_SIZE, MmCached);
if (pvSIMP[i] == NULL){ DbgPrintString("Error during pvSIMP MmMapIoSpace");
return 1;
}
DbgLog16("pvSIMP[i] address", pvSIMP[i]);
pAdr.QuadPart = ArchReadMsr (HV\_X64\_MSR\_SIEFP) & 0xFFFFFFFFFFFFF000;
pvSIEFP[i] = MmMapIoSpace(pAdr, PAGE\_SIZE, MmCached);
if (pvSIEFP[i] == NULL){DbgPrintString("Error during pvSIEFP MmMapIoSpace");
return 1;
}
DbgLog16("pvSIEFP address", pvSIEFP[i]);
}
return 0;
}



Рис. 1. Масив HvlpInterruptCallback з зміненими обробниками

Масив HvlpInterruptCallback після виконання функції RegisterInterrupt (якщо замінити всі обробники одночасно) виглядає наступним чином (див. рис. 1). Заміна виконана за аналогією з оригінальним кодом: один обробник повідомлень гіпервізора і чотири для обробки повідомлень від VMBus. Процедури ArchmWinHvOnInterrupt і ArchXPartEnlightenedIsr виконують збереження всіх регістрів в стеку і передають управління на функції парсинга повідомлень ParseHvMessage і ParseVmbusMessage відповідно (mPUSHAD і mPOPAD — макроси, які виконують збереження регістрів в стеку) (див. рис. 2).


Рис. 2. ArchmWinHvOnInterrupt і ArchXPartEnlightenedIsr

Після виконання парсинга управління передається на оригінальні процедури WinHvOnInterrupt і XPartEnlightenedIsr. Функція парсинга повідомлень гіпервізора виглядає наступним чином:

void ParseHvMessage()
{
PHV\_MESSAGE phvMessage, phvMessage1;
// Отримуємо номер активного логічного процесора
ULONG uCurProcNum = KeGetCurrentProcessorNumberEx(NULL);
Unlock+0x162
vmbkmcl!VmbChannelEnable+0x231
vmbus!PipeStartChannel+0x9e
vmbus!PipeAccept+0x81
vmbus!InstanceCreate+0x90
..................................
nt!IopParseDevice+0x7b3
nt!ObpLookupObjectName+0x6d8
nt!ObOpenObjectByName+0x1e3
nt!IopCreateFile+0x372
nt!NtCreateFile+0x78
nt!KiSystemServiceCopyEnd+0x13
ntdll!NtCreateFile+0xa
KERNELBASE!CreateFileInternal+0x30a
KERNELBASE!CreateFileW+0x66
vmbuspipe!VmbusPipeClientOpenChannel+0x44
icsvc!ICTransportVMBus::ClientNotification+0x60
vmbuspipe!VmbusPipeClientEnumeratePipes+0x1ac
icsvc!ICTransportVMBusClient::Open+0xe5
icsvc!ICEndpoint::Connect+0x66
icsvc!ICChild::Run+0x65
icsvc!ICKvpExchangeChild::Run+0x189
icsvc!ICChild::ICServiceWork+0x137
icsvc!ICChild::ICServiceMain+0x8f
..................................


У віртуальної машини активований компонент Data Exchange.Після встановлення галочки на компоненті Data Exchange і натискання кнопки Apply root ОС через гипервызов HvPostMessage відправляє гостьової ОС повідомлення з кодом CHANNELMSG\_OFFERCHANNEL (див. рис. 9). Передані дані містять GUID пристрою, підключеного до VMBus як дочірнє пристрій (див. рис. 10). Далі гостьова ОС обробляє дані і викликає функцію vmbus!InstanceDeviceControl.


Рис. 9. GUID пристрою в повідомленні


Рис. 10. Висновок !devnode для пристрою VMBus

Частина стека:

WINDBG\>кс
Call Sitent!IoAllocateMdl vmbus!InstanceCloseChannel+0x22d (адреса повернення для функції, ім'я якої відсутня в символах)
vmbus!InstanceDeviceControl+0x118
..................................
vmbkmcl!KmclpSynchronousIoControl+0xa7
vmbkmcl!KmclpClientOpenChannel+0x2a6
vmbkmcl!KmclpClientFindVmbusAnd
if (pvSIMP[uCurProcNum] != NULL){
phvMessage = (PHV\_MESSAGE)pvSIMP[uCurProcNum];
} else{
DbgPrintString("pvSIMP is NULL");
return;
}
// Повідомлення про відправку повідомлення через 1-й слот SIM phvMessage1 = (PHV\_MESSAGE)((PUINT8)pvSIMP[uCurProcNum]+ HV\_MESSAGE\_SIZE); // for SINT1
if (phvMessage1-\>Header.MessageType != 0){
DbgPrintString("SINT1 interrupt");
}
// В залежності від типу повідомлення викликаємо процедури-обробники
// Структури для кожного типу повідомлення описані в TLFS
switch (phvMessage-\>Header.MessageType)
{
case HvMessageTypeX64IoPortIntercept:
PrintIoPortInterceptMessage(phvMessage);
break;
case HvMessageTypeNone:DbgPrintString("HvMessageTypeNone");
break;
case HvMessageTypeX64MsrIntercept:PrintMsrInterceptMessage(phvMessage);
break;
case HvMessageTypeX64CpuidIntercept:PrintCpuidInterceptMessage(phvMessage);
break;
case HvMessageTypeX64ExceptionIntercept:PrintExceptionInterceptMessage(phvMessage);
break;
default:
DbgLog("Unknown MessageType", phvMessage-\>
Header.MessageType);
break;
}
}


Функція отримує номер активного логічного процесора, адреса сторінки SIM і зчитує значення нульового слота SIM. Спершу проводиться аналіз типу повідомлення phvMessage-\>Header.MessageType, оскільки тіло повідомлення для кожного типу різне. У DbgView можна побачити наступну картину (див. рис. 3).


Рис. 3. Висновок DbgView при обробці гіпервізором звернень до MSR-регістрів


Функція ParseVmbusMessage (рис. 4).

Функція отримує номер активного логічного процесора, адреса сторінки SIM і зчитує значення четвертого слота SIM. Для прикладу розібрані повідомлення CHANNELMSG\_OPENCHANNEL і CHANNELMSG\_GPADL\_HEADER, але у вихідних кодах LIS можна побачити формат всіх типів повідомлень і без праці дописати необхідні обробники. Повідомлення для шини VMBus зазвичай генеруються при включенні/виключенні віртуальної машини або ж одного з компонентів Integration Services. Наприклад, при включенні компонента Data Exchange відладчик або DbgView покаже інформацію, зображену на рис. 5.


Рис. 5. Вивід зневадження повідомлень при включенні компонента Data Exchange

Integration Services — Data Exchange
Далі розглянемо, яким же чином відбувається обмін даними між гостьовою і root ОЗ на прикладі одного з компонентів служб інтеграції — Data Exchange. Цей компонент дозволяє root ОС зчитувати дані з певної гілки реєстру гостьової ОС. Для перевірки у гостьовій ОС створимо в гілці HKEY\_LOCAL\_MACHINE\\SOFTWARE\\Microsoft\\Virtual Machine\\Guest ключ зі значенням KvPDataValue (див. рис. 6).


Рис. 6. Ключ KvPDataValue

Для отримання значення ключа в root ОС був використаний наступний PowerShell-скрипт(див. рис. 7).


Рис. 7. PowerShellскрипт для запиту значень реєстру з гостьової ОС

Скрипт поверне значення ключа KvPDataKey (див. рис. 8). Скрипт отримує весь доступний набір значень з допомогою \$vm.GetRelated(«Msvm\_KvpExchangeComponent»).GuestExchangeItems і тільки після цього виконує розбір кожного отриманого об'єкта на предмет пошуку ключа KvPDataKey. Відповідно, скрипт буде працювати тільки в тому випадку, якщо властивості цієї функції викликається nt!IoAllocateMdl з розміром виділяється буфера 0xC000. Результат виконання функції — сформована структура MDL (див. рис. 11). Далі викликається MmProbeAndLockPages, після завершення якої структура MDL доповнюється елементами PFN (див. рис. 12). В даному прикладі була виділена безперервна область фізичної пам'яті, хоча ця умова виконується необов'язково. Далі викликається vmbus!ChCreateGpadlFromNtmdl (другим параметром передається адреса MDL), яка викликає vmbus!ChpCreateGpaRanges, передаючи їй в якості першого параметра все той же MDL. Далі виконується копіювання елементів PFN з структури MDL в окремий буфер (див. рис. 13), який стане тілом повідомлення CHANNELMSG\_GPADL\_HEADER, відправляється гостьової ОС root ОС допомогою виклику vmbus!ChSendMessage. hv!HvPostMessage або в winhv!WinHvPostMessage можна побачити повідомлення (рис.14).


Рис. 8. Результат виконання скрипта


Рис. 11. Значення структури MDL


Рис. 12. PFN в MDLструктуре


Рис. 13. Ділянка коду, що відповідає за копіювання PFN в окремий буфер


Рис. 14. Масив PFN, оброблюваний гіпервізором

Перші 16 байт — це загальний заголовок повідомлення, де, наприклад, 0xF0 — розмір тіла повідомлення, всередині розміщується VMBus-пакет, в заголовку якого вказаний тип пакета — 8 (CHANNELMSG_GPADL_HEADER), rangecount дорівнює 1, з чого випливає, що в один пакет вмістилися всі дані, які необхідно було передати. У разі великого обсягу даних драйвер гостьової ОС розділив би їх на частини і відправив окремими повідомленнями. Далі root ОС шле повідомлення CHANNELMSG\_OPENCHANNEL\_RESULT, потім гостьова ОС шле CHANNELMSG\_OPENCHANNEL. Після цього root ОС відпрацьовує Work Item (див. рис. 15).


Рис. 15. Виклик ChMapGpadlView

У ході його виконання викликається vmbusr!ChMapGpadlView->vmbusr!PkParseGpaRanges, якій, у свою чергу, передається покажчик на частину повідомлення, що містить розмір буфера 0xC000 і PFN, передані в повідомленні CHANNELMSG\_GPADL\_HEADER. Далі відбувається виклик vmbusr!XPartLockChildPagesSynchronous-\>vmbusr!XPartLockChildPages, після чого виконується функція драйвер vid.sys (ім'я функції невідомо, оскільки символи для драйвера відсутні), якій в якості другого параметра передається блок PFN, відправлений раніше в повідомленні з гостьової ОС (див. рис. 16).


Рис. 16. Обробка гостьових PFN драйвером vid.sys

Безпосередньо після повернення з функції [rsp+30h] знаходиться вказівник на новостворену структуру MDL (див. рис. 17).


Рис. 17. Структура MDL, повертається драйвером vid.sys

Розмір виділеного буфера також дорівнює 0xC000. Після цього root ОС шле повідомлення CHANNELMSG\_OPENCHANNEL\_RESULT. На цьому процес активації компонента Data Exchange завершується. Таким чином створюється якийсь shared-буфер, видимий як гостьовий, так і root ОС. Це можна перевірити, виконавши запис довільних даних в буфер у гостьовій ОС, наприклад з допомогою команди:

WINDBG\>!ed 2d5bb000 aaaaaaaa
WINDBG\>!db 2d5bb000
\#2d5bb000 aa aa aa aa 10 19 00


А в root ОС подивитися вміст сторінки, відповідної PFN, поверненої функцією драйвера vid.sys:

**WINDBG\>!db 1367bb000**
\#1367bb000 aa aa aa aa 10 19


Як видно, значення збіглися, так що це дійсно одна і та ж фізична область пам'яті. Згадаймо, що на попередніх етапах ми визначили, що при активації компонента Data Exchange створюється порт типу HvPortTypeEvent з TargetSint = 5. Відповідно, всі операції з цим портом в root ОС буде обробляти KiVmbusInterrupt1, з якої відбувається виклик vmbusr!XPartEnlightenedIsr, а вона, в свою чергу, викликає KeInsertQueueDpc з параметром DPC (його значення показано на рис. 19).


Рис. 19. Значення DPC, яку ставить в чергу XPartEnlightenedIsr

З vmbusr!ParentRingInterruptDpc через кілька викликів буде виконана vmbusr!PkGetReceiveBuffer.

WINDBG\>k
Child-SP RetAddr Call Site
fffff800\`fcc1ea38 fffff800\`6cdc440c
vmbusr!PkGetReceiveBuffer+0x2c
fffff800\`fcc1ea40 fffff800\`6cdc41a7
vmbusr!PipeTryReadSingle+0x3c
fffff800\`fcc1eaa0 fffff800\`6cdc4037
vmbusr!PipeProcessDeferredReadWrite+0xe7
fffff800\`fcc1eaf0 fffff800\`6c96535e
vmbusr!PipeEvtChannelSignalArrived+0x63
fffff800\`fcc1eb30 fffff800\`6cdc4e3d
vmbkmclr!KmclpVmbusManualIsr+0x16
fffff800\`fcc1eb60 fffff800\`fb2d31e0
vmbusr!ParentRingInterruptDpc+0x5d


Якщо переглянути цю область пам'яті, то стають видні параметри гостьової ОС.

WINDBG\> dc ffffd0016fe33000 L1000
.................................................................................................................................
ffffd001\`6fe35b30 0065004e 00770074 0072006f
0041006b N. e.t.w.o.r.k.A.
ffffd001\`6fe35b40 00640064 00650072 00730073
00500049 d.d.r.e.s.s.I.P.
ffffd001\`6fe35b50 00340076 00000000 00000000 00000000
v.4.............
.................................................................................................................................
ffffd001\`6fe35d20 00000000 00000000 00000000 00000000
................
ffffd001\`6fe35d30 00300031 0030002e 0030002e 0033002e
1.0...0...0...3.
ffffd001\`6fe35d40 00000000 00000000 00000000 00000000
................
WINDBG\>!pte ffffd001\`6fe35b30
VA ffffd0016fe35b30
PXE at FFFFF6FB7DBEDD00 PPE at FFFFF6FB7DBA0028 PDE at
FFFFF6FB74005BF8
PTE at FFFFF6E800B7F1A8
contains 0000000000225863 contains 00000000003B7863 contains
000000010FB12863 contains 80000001367BD963
pfn 225 ---DA--KWEV pfn 3b7 ---DA--KWEV pfn 10fb12 ---DA-KWEV pfn
1367bd-G-DA--KW-V
pfn 1367bd - це PFN 3-ї сторінки з конвертованого MDL


Також цієї ж функції в rdx передається покажчик, що містить зсув щодо адреси початку загальних з гостьової ОС сторінок (на прикладі він дорівнює 4448h), за яким необхідно здійснити читання:

vmbusr!PkGetReceiveBuffer+0x4e:
mov r8,r10 (в r10d був раніше завантажено зміщення з rdx)
add r8,qword ptr [rcx+20h] - в rcx+20 міститься курсор на одну з загальних з гостьової ОС сторінку
WINDBG\>!pte @r8
VA ffffd0016ff22448
PXE at FFFFF6FB7DBEDD00 PPE at FFFFF6FB7DBA0028 PDE at FFFFF6FB74005BF8
PTE at FFFFF6E800B7F910
contains 0000000000225863 contains 00000000003B7863 contains
000000010FB12863 contains 80000001367C0963
pfn 225 ---DA--KWEV pfn 3b7 ---DA--KWEV pfn 10fb12 ---DA-KWEV pfn
1367c0-G-DA--KW-V


Поставимо точку зупину на початок функції vmbusr!PkGetReceiveBuffer і виконаємо PowerShell-скрипт. Точка зупинки спрацює, при цьому буде видно, що функції передається структура (покажчик у rcx) і в rcx+18 знаходиться вказівник на блок пам'яті:


WINDBG\>? poi(@rcx+18)
Evaluate expression: -52770386006016 = ffffd001\`6fe33000
WINDBG\>!pte ffffd001\`6fe33000
VA ffffd0016fe33000
PXE at FFFFF6FB7DBEDD00 PPE at FFFFF6FB
7DBA0028 PDE at FFFFF6FB74005BF8
PTE at FFFFF6E800B7F198
contains 0000000000225863 contains
00000000003B7863 contains
000000010FB12863 contains
80000001367BB963
pfn 225 ---DA--KWEV pfn 3b7 ---DA--KWEV
pfn 10fb12 ---DA--KWEV pfn
1367bb-G-DA--KW-V
WINDBG\>r cr3
cr3=00000000001ab000
WINDBG\>!vtop 1ab000 ffffd 0016fe33000
Amd64VtoP: Virt ffffd001\`6fe33000,
pagedir 1ab000
Amd64VtoP: PML4E 1abd00
Amd64VtoP: PDPE 225028
Amd64VtoP: PDE 3b7bf8
Amd64VtoP: PTE 00000001\`0fb12198
Amd64VtoP: Mapped phys 00000001\`367bb000
Virtual address ffffd0016fe33000 translates to
physical address
1367bb000.


INFO
Інформацію про технології KvP можна знайти в блогах MSDN:
goo.gl/R0U52l
goo.gl/UeZRK2

Якщо поставити точку зупину на інструкцію add r8,qword ptr [rcx+20h], то через кілька ітерацій у r8 можна побачити ім'я і значення ключа KvpDataKey:

WINDBG\>dc @r8
ffffd001\`6ff21d10
....H...........
ffffd001\`6ff21d20
....(........... ffffd001\`6ff21d30

00020006 00000148 00000000 00000000
00000001 00000 a28 00000003 00050002
розмір передаваного блоку
0a140000 00000000 00000515 00000103
................
ffffd001\`6ff21d40 00000004 00000001 00000016 0000001a
................
ffffd001\`6ff21d50 0076004b 00440050 00740061 004b0061
K. v.P.D.a.t.a.K.
ffffd001\`6ff21d60 00790065 00000000 00000000 00000000
e.y.............
.............................................................
..........
ffffd001\`6ff21f50 0076004b 00440050 00740061 00560061
K. v.P.D.a.t.a.V.
ffffd001\`6ff21f60 006c0061 00650075 00000000 00000000
a.l.u.e.........
WINDBG\>!pte ffffd001\`6ff21f50
VA ffffd0016ff21f50
PXE at FFFFF6FB7DBEDD00 PPE at FFFFF6FB7DBA0028 PDE at
FFFFF6FB74005BF8
PTE at FFFFF6E800B7F908
contains 0000000000225863 contains 00000000003B7863 contains
000000010FB12863 contains 80000001367BF963
pfn 225 ---DA--KWEV pfn 3b7 ---DA--KWEV pfn 10fb12 ---DA-KWEV pfn
1367bf-G-DA--KW-V


Потім після завершення PkGetReceiveBuffer функція PipeTryReadSingle копіює дані з shared-буфера за допомогою функції memmove.

При цьому розмір блоку (в даному випадку A28) вказано безпосередньо в самому блоці, але якщо буде задано число більше, ніж 4000h, то копіювання не буде вироблено. Таким чином, видно, що обмін даними між root ОС і гостьової ОС використовує загальний буфер, а інтерфейс гіпервізора використовується лише для повідомлення root ОС про те, що необхідно виконати зчитування даних з цього буфера. У принципі, ту ж операцію можна виконати за допомогою відправки декількох повідомлень, використовуючи winhv!HvPostMessage, але це призвело б до значного зниження продуктивності.

Використання інтерфейсу перехоплення гіпервізора

Налаштуємо гіпервізор таким чином, щоб він відправляв повідомлення root ОС у випадку, якщо в одній з гостьової ОС виконується інструкція cpuid з параметром 0x11114444. Для цього Hyper-V надає інтерфейс у вигляді гипервызова HvInstallIntercept. В драйвері hyperv4 реалізована функція SetupIntercept, яка отримує список ідентифікаторів усіх активних гостьових ОС і викликає для кожної WinHvInstallIntercept.

int SetupIntercept()
{
HV\_INTERCEPT\_DESCRIPTOR Descriptor;
HV\_INTERCEPT\_PARAMETERS Parameters = {0};
HV\_STATUS hvStatus = 0;
HV\_PARTITION\_ID PartID = 0x0, NextPartID = 0;
// Якщо у якості параметра інструкції RAX-інструкції CPUID буде передано значення 0x11114444, то гіпервізор виконає перехоплення і відправить повідомлення батьківського розділу для обробки результату
DbgPrintString("SetupInterception was called");
Parameters.CpuidIndex = 0x11114444;
Descriptor.Type = HvInterceptTypeX64Cpuid;
Descriptor.Parameters = Parameters;
hvStatus = WinHvGetPartitionId(&PartID);
do{
hvStatus = WinHvGetNextChildPartition(PartID,NextPartID,&NextPartID);
if (NextPartID != 0){
DbgLog("Child partition id", NextPartID);
hvStatus = WinHvInstallIntercept(NextPartID,
HV\_INTERCEPT\_ACCESS\_MASK\_EXECUTE, &Descriptor);
DbgLog("hvstatus of WinHvInstallIntercept = ",hvStatus);
} } while ((NextPartID != HV\_PARTITION\_ID\_INVALID) &&
(hvStatus == 0));
return 0;}



Також змінимо функцію PrintCpuidInterceptMessage таким чином, щоб вона в разі, якщо в гостьовій ОС в регістрі EAX (або RAX, якщо код, який виконує інструкцію CPUID, виконується в longmode) знаходиться число 0x11114444, записувала в поле DefaultResultRdx структури HV_X64_CPUID_INTERCEPT_MESSAGE, розташовану в нульовому слоті SIM, значення 0x12345678:

void PrintCpuidInterceptMessage(PHV\_MESSAGE hvMessage)
{PHV\_X64\_CPUID\_INTERCEPT\_MESSAGE_phvCPUID = (PHV\_X64\_CPUID\_NTERCEPT\_MESSAGE)_hvMessage-\>Payload;
DbgLog(" phvCPUID-\>DefaultResultRax",phvCPUID-\>DefaultResultRax);
DbgLog(" phvCPUID-\>DefaultResultRbx",phvCPUID-\>DefaultResultRbx);
DbgLog(" phvCPUID-\>DefaultResultRcx",phvCPUID-\>DefaultResultRcx);
DbgLog(" phvCPUID-\>DefaultResultRdx",phvCPUID-\>DefaultResultRdx);
if (phvCPUID-\>Rax == 0x11114444){
phvCPUID-\>DefaultResultRdx =0x12345678;
DbgLog16(" phvCPUID-\>Header.Rip",phvCPUID-\>Header.Rip);
DbgPrintString(" Interception was handled");
}
}


Для перевірки у гостьовій ОС запустимо тестову утиліту, яка викликає CPUID з Eax, рівним 0x11114444. До установки перехоплення утиліта виведе результат, відображений на рис. 20.


Рис. 20. Результат інструкції CPUID на звичайній гостьової ОС

Після активації перехоплення результат буде наступним (див. рис. 21).


Рис. 21. Результат інструкції CPUID після установки перехоплення

При цьому в root ОС буде виведено повідомлення (див. рис. 22).


Рис. 22. Вивід зневадження обробки повідомлення гіпервізора при встановленому перехопленні

Відразу варто звернути увагу на те, що цей трюк пройде тільки в тому випадку, якщо root ОС раніше не встановила перехоплення для заданих умов. У цьому випадку після того, як драйвер hyperv замінить значення, управління перейде на оригінальну WinHvOnInterrupt, яка викличе функцію обробки драйвер vid.sys (ця функція є четвертим параметром функції winhvr!WinHvCreatePartition, викликається в root ОС при створенні дочірнього розділу при включенні віртуальної машини), що може призвести до зміни результату. У нашому випадку такий обробник, зрозуміло, не встановлено, гіпервізор проаналізував дані в нульовому слоті SIM і виправив результат інструкції CPUID.

висновок
Незважаючи на те, що після прочитання моєї праці твій мозок напевно став у позу річкового скорпіона (і якщо ти взагалі досюда дочитав — респект тобі від усієї нашої редакції)… так, я відволікся. Ця стаття вийшла швидше оглядової, що демонструє роботу деяких функцій і компонентів системи віртуалізації Microsoft на прикладах. Проте, сподіваюся, ці приклади допоможуть краще зрозуміти принципи роботи цих компонентів і дозволять більш глибоко проаналізувати безпеку, наприклад VMBus, написавши свій власний фаззер.

Автор: gerhart



Вперше надруковано в журналі «Хакер» від 12/2014.

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






Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.