Содержание статьи
Технология виртуализации Hyper-V, представленная компанией Microsoft довольно давно, получает немало дополнений и улучшений с каждым новым выпуском серверной версии Windows. Так, в Windows Server 2016 был интегрирован новый протокол коммуникаций под названием сокеты Hyper-V, который позволяет подключаться к виртуальным машинам из родительской операционной системы, используя в качестве транспорта не привычный стек TCP/IP, а шину VMBus. Первое ее применение было реализовано в технологии PowerShell Direct. Помимо этого, Microsoft предоставила разработчикам возможность использовать в своих продуктах сокеты Hyper-V, выпустив новую версию SDK для Visual Studio. Прочитав эту статью, ты узнаешь особенности работы сокетов Hyper-V, поймешь, каким образом они используются в технологии PowerShell Direct и как реализовано их взаимодействие с ядром Windows.
Термины и определения
- Root-раздел (родительский раздел, root ОС) — Windows Server 2016 с установленным компонентом Hyper-V.
- Гостевая ОС (дочерний раздел, гостевая ОС, guest ОС) — виртуальная машина c Windows Server 2016 Gen2.
- Hyper-V TLFS – Hyper-V Top Level Functional Specification.
Введение
2006 год. Конференция WinHEC. Microsoft активно продвигает свой вариант гипервизора (еще даже без названия, его обозначали просто как Windows hypervisor) и намекает на то, что разработчики смогут создавать свои решения на базе новой технологии виртуализации.
Действительно, в этом направлении были сделаны определенные шаги, и у разработчиков появились:
- заголовочные файлы
hvgdk.h
,vid.h
,VidDefs.h
(Windows WDK 6.0, 7.1, Singularity ОС); - Hyper-V Top Level Functional Specification (TLFS);
- документация на MSDN, которая в основном совпадала с TLFS, но содержала более детальную информацию.
На osronline.com архитектор Hyper-V Джейк Ошинс (Jake Oshins) отвечал на вопросы разработчиков драйверов, касающиеся среды Hyper-V.
Все же опубликованной информации было явно недостаточно для того, чтобы кто-то начал разрабатывать новые продукты на базе Hyper-V (вспоминается только LiveCloudKd от moonsols, да и то, похоже, большую часть информации разработчики просто отреверсили). Возможно, в связи с этим политика Microsoft резко изменилась:
- заголовочные файлы были убраны из WDK;
- исчезла документация из MSDN, связанная с Hyper-V (на osronline.com для формальности провели опрос, нужна документация или нет);
- из WinDBG исчезли расширения, связанные с Hyper-V (network virtualization kernel debugger extension nvkd.dll),
hvexts.dll
, на который ссылался WinDBG при подключении кhvix64.exe
(hvax64.exe
), так и не был выложен в общий доступ; - архитектор Hyper-V Джейк Ошинс исчез с форума osronline.com.
Тем не менее Microsoft самостоятельно стала разрабатывать Linux Integration Services — набор позволяющих запускать Linux внутри Hyper-V компонентов и драйверов, исходные коды которых интегрированы в ядро Linux (2.6.32 и выше) и, соответственно, выложены в открытый доступ.
TLFS оставался единственным источником информации о внутреннем устройстве гипервизора, однако выпущенная в феврале 2017 года спецификация для Windows Server 2016 содержит уже 238 страниц, а не 420, как это было в предыдущей спецификации для Windows Server 2012 R2 (из 23 разделов осталось 16, исчезли описания многих гипервызовов, однако появились два раздела, описывающих работу VSM (Virtual Secure Mode) и вложенной виртуализации).
Но в 2016 году заголовочный файл ws2def.h
(core definitions for the Winsock2 specification) в Windows SDK 10.0.10586 дополнили строчкой
#define AF_HYPERV 34
а в Windows SDK 10.0.14393 появился файл HvSocket.h. В каких же целях это было сделано?
В Windows Server 2016 добавили новую функцию — PowerShell Direct, которая позволяет выполнять PowerShell-команды в гостевой операционной системе без сетевого соединения, передавая все необходимые данные через шину VMBus. Этот механизм работает, используя так называемые сокеты Hyper-V, которые были интегрированы в сетевой стек Windows.
Статья, которую ты читаешь, стала результатом попытки понять, как работает механизм сетевого взаимодействия в Windows, каким образом в него встроили поддержку сокетов Hyper-V и что именно выполняет операционная система при работе нового протокола.
Рассмотрим, каким образом подсистема виртуализации была интегрирована с сетевым стеком Windows, затем разберем работу приложения, использующего сокеты Hyper-V, и узнаем, как устроена технология PowerShell Direct. Windows 10 тоже поддерживает такие сокеты, но далее она практически не рассматривается — акцент сделан именно на серверную ОС, хотя предположительно существенной разницы в реализации быть не должно.
Перед прочтением статьи рекомендуем ознакомиться с разделом 7 «Сеть» книги «Внутреннее устройство Microsoft Windows», 6-е издание, часть 1 (в 5-м издании книги этот раздел еще не был опубликован), а также со статьей «Сетевой программный интерфейс Windows Vista/2008: внутреннее устройство, использование и взлом», ранее доступной на wasm.ru. Теперь же ее можно найти на различных сайтах (например, тут). MSDN довольно подробно освещает затронутые в статье темы, но без привязки к сокетам Hyper-V.
Компоненты операционной системы
Сперва посмотрим список провайдеров (Layered Service Provider — LSP), установленных в операционной системе. Видим две записи, в имени которых содержится Hyper-V.
PS C:\Windows\system32> netsh winsock show catalog
Winsock Catalog Provider Entry
------------------------------------------------------
Entry Type: Base Service Provider
Description: Hyper-V RAW
Provider ID: {1234191B-4BF7-4CA7-86E0-DFD7C32B5445}
Provider Path: %SystemRoot%\system32\mswsock.dll
Catalog Entry ID: 1001
Version: 2
Address Family: 34
Max Address Length: 36
Min Address Length: 36
Socket Type: 1
Protocol: 1
Service Flags: 0x20026
Protocol Chain Length: 1
Если расшифровать Service Flags в соответствии с описанием структуры WSAPROTOCOL_INFO
из MSDN, то получим 0x20026 = XP1_GUARANTEED_DELIVERY | XP1_GUARANTEED_ORDER | XP1_GRACEFUL_CLOSE | XP1_IFS_HANDLES
.
В реестре, соответственно, для каждого провайдера (32-битного и 64-битного) создано по одному разделу:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WinSock2\Parameters\Protocol_Catalog9\Catalog_Entries\000000000001 и HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WinSock2\Parameters\Protocol_Catalog9\Catalog_Entries64\000000000001
В разделе HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Winsock\Parameters
был добавлен транспорт VMBus (irda и RFCOMM в Windows Server 2016 в инсталляции по умолчанию отсутствуют и есть только в Windows 10).
Ключ HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\vmbus\Parameters
содержит подраздел Winsock с параметром HelperDllName, в котором указано имя библиотеки wshhyperv.dll
, подгружаемой основным провайдером mswsock.dll
на этапе создания сокета.
В 6-м издании книги Windows Internals написано, что «транспортный протокол RAW не является настоящим протоколом и не осуществляет никакой инкапсуляции пользовательских данных. Это позволяет клиенту непосредственно контролировать содержимое фреймов, отправляемых и получаемых по сетевому интерфейсу». В нашем случае используется протокол Hyper-V RAW. Однако, несмотря на то что в названии протокола содержится RAW, при вызове функции socket в качестве второго ее параметра (тип сокета) указывается SOCK_STREAM (stream socket), хотя в WinSock2.h
присутствует отдельный тип сокета — SOCK_RAW.
Появился новый NPI-провайдер — hvsocket.sys
. Он присутствует в импорте драйверов vmbus.sys
, vmbusr.sys
и netio.sys
и загружается вместе с первым из указанных модулей. Регистрируется как провайдер драйвером vmbusr.sys при вызове импортированной функции hvsocket!HvSocketProviderStart
, которая, в свою очередь, вызывает netio!NmrRegisterProvider
. Подробно работа с провайдерами была описана в упомянутой выше статье с WASM.
С помощью WinDBG можно получить список всех регистрируемых провайдеров и их клиентов. Для провайдеров достаточно написать скрипт (ставим bp на netio!NmrRegisterProvider
и записываем параметры в лог):
__Windbg> bu netio!NmrRegisterProvider__
__Windbg>.logopen D:\ida_files\2016\log.txt__
__Windbg>bp netio!NmrRegisterProvider ".echo \**********bp netio!NmrRegisterProvider\********; .echo kc;kc; .echo dps rcx;dps rcx; .echo NpiId GUID; dt _GUID poi(rcx+28h); .echo NPI_MODULEID_TYPE GUID; dt _GUID poi(rcx+30h)+8; g"__
NTSTATUS NmrRegisterProvider(
_In_ PNPI_PROVIDER_CHARACTERISTICS ProviderCharacteristics,
_In_ PVOID ProviderContext,
_Out_ PHANDLE NmrProviderHandle
);
typedef struct _NPI_REGISTRATION_INSTANCE {
USHORT Version;
USHORT Size;
PNPIID NpiId;
PNPI_MODULEID ModuleId;
ULONG Number;
const VOID *NpiSpecificCharacteristics;
} NPI_REGISTRATION_INSTANCE, *PNPI_REGISTRATION_INSTANCE;
typedef struct _NPI_MODULEID {
USHORT Length;
NPI_MODULEID_TYPE Type;
union {
GUID Guid;
LUID IfLuid;
};
} NPI_MODULEID, *PNPI_MODULEID;
Регистрация hvsocket.sys
как провайдера будет выглядеть так:
__kc__
# Call Site
00 NETIO!NmrRegisterProvider
01 hvsocket!HvSocketProviderStart
02 vmbusr!RootDeviceAdd
03 Wdf01000!FxDriverDeviceAdd::Invoke
04 Wdf01000!FxDriver::AddDevice
05 nt!PpvUtilCallAddDevice
06 nt!PnpCallAddDevice
07 nt!PipCallDriverAddDevice
08 nt!PipProcessDevNodeTree
09 nt!PiProcessStartSystemDevices
0a nt!PnpDeviceActionWorker
0b nt!ExpWorkerThread
0c nt!PspSystemThreadStartup
0d nt!KiStartSystemThread
__dps rcx__
fffff806`0dece010 00000000`00480000
fffff806`0dece018 fffff806`0ded1640 hvsocket!HvSocketNotifyAttachClient - ProviderAttachClient
fffff806`0dece020 fffff806`0ded18c0 hvsocket!HvSocketNotifyDetachClient - ProviderDetachClient
fffff806`0dece028 fffff806`0ded19a0 hvsocket!HvSocketNotifyCleanupClientContext - ProviderCleanUpBindingContext
fffff806`0dece030 00000000`00280000 - Begin of NPI_REGISTRATION_INSTANCE (Version+Size)
fffff806`0dece038 fffff806`0decc3e0 hvsocket!NPI_TRANSPORT_LAYER_ID - pointer to NpiId (GUID NPIID) - dt _GUID poi(rcx+28h)
fffff806`0dece040 fffff806`0decc3f0 hvsocket!NPI_MS_VMBUS_MODULEID - pointer to ModuleId - dt _GUID poi(rcx+30h)+8
fffff806`0dece048 00000000`00000000 - Number
fffff806`0dece050 fffff806`0decc2e0 hvsocket!VmbusTlProviderCharacteristics - NpiSpecificCharacteristics
fffff806`0dece058 00000000`00000000
fffff806`0dece060 00000500`00000000
fffff806`0dece068 0000ef8b`4509d61c
fffff806`0dece070 00000000`00000000
fffff806`0dece078 00000000`00000000
fffff806`0dece080 00000000`00000000
fffff806`0dece088 fffff803`6c322884 nt!EtwRegisterClassicProvider
__NpiId GUID__
ntdll!_GUID
{2227e804-8d8b-11d4-abad-009027719e09}
+0x000 Data1 : 0x2227e804
+0x004 Data2 : 0x8d8b
+0x006 Data3 : 0x11d4
+0x008 Data4 : [8] "???"
NPI_MODULEID_TYPE GUID
ntdll!_GUID
{eb004a27-9b1a-11d4-9123-0050047759bc}
+0x000 Data1 : 0xeb004a27
+0x004 Data2 : 0x9b1a
+0x006 Data3 : 0x11d4
+0x008 Data4 : [8] "???"
Аналогично выполняется логирование регистрации NPI-клиентов. Единственное отличие — bp нужно ставить на netio!NmrRegisterClient
. Интересно, что регистрация hvsocket.sys
как клиента нигде не замечена. Единственные регистрируемые компоненты виртуализации — это NDIS!NPI_NDIS_VBUS_INTERFACE_ID (регистрация идет из NDIS!DriverEntry) и vmswitch!NPI_PKTCAP_INTERFACE_ID (регистрация из vmswitch!DriverEntry).
Полный список провайдеров и клиентов, регистрируемых Windows Server 2016, размещен на GitHub. Список провайдеров предоставлен в следующем формате.
В afd.sys
есть функция afd!AfdTlNotifyAttachProvider
(client module’s ClientAttachProvider callback function), которая работает со структурой AfdTlTransportListHead
. Небольшой скрипт для pykd, который выводит часть элементов address family и функцию обработки каждого элемента:
__2: kd> !py D:\afd_parse_AfdTlTransportListHead.py__
cs:AfdTlTransportListHead address is 0xffffb089b4579040L
----Address family 0x0 [ AF_UNSPEC ]
--Dispatch function tcpip!TcpTlProviderDispatch
----Address family 0x0 [ AF_UNSPEC ]
--Dispatch function tcpip!UdpTlProviderDispatch
----Address family 0x0 [ AF_UNSPEC ]
--Dispatch function tcpip!RawTlProviderDispatch
----Address family 0x22 [ AF_HYPERV ]
--Dispatch function hvsocket!VmbusTlProviderDispatch
В принципе, аналогичную информацию должна выводить команда afd плагина mex для WinDBG, но на моем стенде она по какой-то причине не сработала (возможно, необходимы private symbols).
При старте vmbusr.sys
видим запуск hvsocket!HvSocketProviderStart
, после которой вызывается afd!AfdTlNotifyAttachProvider
:
__0>kc__
# Call Site
00 NETIO!NmrClientAttachProvider
01 afd!AfdTlNotifyAttachProvider
02 NETIO!NmrpProposeAttachment
03 NETIO!NmrpAttachArray
04 NETIO!NmrpRegisterModule
05 NETIO!NmrRegisterProvider
06 hvsocket!HvSocketProviderStart
07 vmbusr!RootDeviceAdd
В hvsocket!HvSocketProviderStart
происходит вызов следующих функций:
- netio!NetioInitializeWorkQueue;
- netio!NmrRegisterProvider;
- hvsocket.sys — NPI-провайдер.
В netio!NmrpVerifyModule к упомянутым в статье с WASM проверкам добавился драйвер hvsocket.sys
.
RtlInitString(&DestinationString, "\\systemroot\\system32\\drivers\\afd.sys");
RtlInitString(&v26, "\\systemroot\\system32\\drivers\\tdx.sys");
RtlInitString(&v24, "\\systemroot\\system32\\drivers\\tcpip.sys");
RtlInitString(&v27, "\\systemroot\\system32\\drivers\\hvsocket.sys");
С помощью скрипта afd_parse_AfdEndpointListHead_pykd.py в системе можно просмотреть список объектов, создаваемых при открытии каждого сокета. Если сокет закрывается, то объект исчезает из списка. Скрипт, в принципе, отображает то же самое, что и утилита tcpconnect из Sysinternals Suite (но она, к сожалению, не показывает открытые Hyper-V сокеты) или WinDBG-скрипт для отображения AFD endpoints, с дополнительным выводом драйвера и процедуры, обрабатывающей операции с сокетом, а также имени процесса и PID.
Например, содержимое списков в гостевой и root ОС после успешного выполнения командлета Enter-PSSession:
__kd> !py C:\Tools\Scripts\afd_parse_AfdEndpointListHead_pykd.py — в гостевой ОС__
afd!AfdEndpointListHead address is 0xfffff80490ef74e0L
----AfdEndpoint 0xfffff80490ef74e0L 0xda10
----AfdEndpoint 0xffffd00d64e7c130L 0xafd2 tcpip!TcpTlProviderEndpointDispatch explorer.exe 0x3c0
----AfdEndpoint 0xffffd00d6421af60L 0xafd2 hvsocket!VmbusTlProviderEndpointDispatch powershell.exe 0x920
----AfdEndpoint 0xffffd00d64846ea0L 0xafd4 hvsocket!VmbusTlProviderListenDispatch powershell.exe 0x920
----AfdEndpoint 0xffffd00d64d082a0L 0xafd1 tcpip!UdpTlProviderEndpointDispatch lsass.exe 0x204
----AfdEndpoint 0xffffd00d642b8960L 0xafd1 tcpip!UdpTlProviderEndpointDispatch lsass.exe 0x204
----AfdEndpoint 0xffffd00d640c8ba0L 0xafd4 hvsocket!VmbusTlProviderListenDispatch svchost.exe 0x35c (в состав процесса входит служба vmicsession)
----AfdEndpoint 0xffffd00d650c3c30L 0xaafd 0 explorer.exe 0x3c0
----AfdEndpoint 0xffffd00d64340f60L 0xaafd 0 explorer.exe 0x3c0
__kd> !py D:\ida_files\afd_parse_AfdEndpointListHead_pykd.py — в родительской ОС__
afd!AfdEndpointListHead address is 0xfffff807668b74e0L
----AfdEndpoint 0xfffff807668b74e0L 0xe4a0
----AfdEndpoint 0xffff958f202eb300L 0xafd2 hvsocket!VmbusTlProviderEndpointDispatch powershell.exe 0xcc4L
----AfdEndpoint 0xffff958f21d7eac0L 0xafd1 tcpip!UdpTlProviderMessageDispatch svchost.exe 0x438L
----AfdEndpoint 0xffff958f207dd9e0L 0xafd2 hvsocket!VmbusTlProviderEndpointDispatch powershell_ise 0x394L
----AfdEndpoint 0xffff958f1fbc5f60L 0xafd1 tcpip!UdpTlProviderMessageDispatch svchost.exe 0x498L
Что интересно, мы можем увидеть еще один сокет, создаваемый процессом svchost.exe на ранних этапах загрузки операционной системы:
__kd> !py D:\ida_files\ParseAfdEndpointListHead.py__
---------------------------------------------------------------------------------------------
----AfdEndpoint 0xffffd1851637e130L 0xafd0 tcpip!TcpTlProviderEndpointDispatch svchost.exe 0x410L
----AfdEndpoint 0xffffd1851627ed60L 0xafd0 hvsocket!VmbusTlProviderEndpointDispatch svchost.exe 0x384L
----AfdEndpoint 0xffffd185161d7330L 0xafd0 tcpip!TcpTlProviderEndpointDispatch wininit.exe 0x2a4L
Этот сокет создается сервисом RPC, а именно функцией RPCRT4!TransportProtocol::HandlePnPStateChange
. Стек:
__kd> k — bp on wshhyperv.dll load__
13 mswsock!SockGetTdiName+0x2b1
14 mswsock!SockSocket+0x117
15 mswsock!WSPSocket+0x220
16 WS2_32!WSASocketW+0x1f0
17 RPCRT4!TransportProtocol::OpenAddressChangeRequestSocket+0x43
18 RPCRT4!TransportProtocol::VerifyProtocolIsFunctional+0x14
19 RPCRT4!TransportProtocol::HandleProtocolChange+0x100
1a RPCRT4!TransportProtocol::HandlePnPStateChange+0x72
1b RPCRT4!ProcessNewAddressEvent+0x21
1c RPCRT4!COMMON_AddressChangeThreadPoolCallback+0x25
1d KERNELBASE!BasepTpIoCallback+0x50
1e ntdll!TppIopExecuteCallback+0x118
1f ntdll!TppWorkerThread+0x8ed
20 KERNEL32!BaseThreadInitThunk+0x14
21 ntdll!RtlUserThreadStart+0x21
В функции RPCRT4!TransportProtocol::HandlePnPStateChange
вызывается ws2_32!WSAEnumProtocols
, по результатам которой производится перебор протоколов в таблице rpcrt4!TransportProtocolArray
. Для каждого элемента таблицы вызывается TransportProtocol::HandleProtocolChange
(второй параметр — структура WSAPROTOCOL_INFOW). Размер каждого элемента TransportProtocolArray
— 72 байта. Но тип этого сокета — 0xafd0. Структура _AFD_CONNECTION, описывающая состояние такого сокета, до конца не заполнена, и по смещению +e0 от начала размещения структуры есть только нули. Чтобы была возможность подключаться к сокетам Hyper-V, его тип должен быть по крайней мере 0xafd2.
В символах rpcrt4.dll
присутствует достаточно много функций, работающих с сокетами Hyper-V.
HVSOCKET_QueryClientID
HVSOCKET_BuildAddressVector
HVSOCKET_Open
HVSOCKET_QueryClientAddress
HVSOCKET_REsolveAddress
HVSOCKET_ResolveVmId
HVSOCKET_ServerListen
HVSOCKET_SetSocketOption
Цели добавления поддержки в библиотеку RPC неизвестны. В соответствии с MSDN PowerShell Direct должен работать локально, да и фактически оказывается, что RPC он не использует.
Возможно, эти функции необходимы для работы среды в контейнерах Docker либо для будущей поддержки удаленной работы PowerShell Direct.
Работа сокетов Hyper-V
На MSDN лишь одна страница описывает шаги, которые нужно выполнить, чтобы создать свое приложение для работы с сокетами Hyper-V. В качестве примера возьмем простое приложение, найденное на просторах интернета и демонстрирующее работу с обычными сокетами, и модифицируем его таким образом, чтобы оно для передачи данных использовало сокеты Hyper-V. Приложение состоит из клиентской и серверной части; клиентская часть передает серверу текст, набранный в консоли, используя для коммуникации сокеты Hyper-V.
В соответствии с MSDN сокеты Hyper-V поддерживают следующие вызовы: socket, bind, connect, send, listen, accept.
Однако на практике видно, что поддерживается большее число команд.
Серверная часть приложения выполняет socket, bind, listen, accept, recv, closesocket.
Клиентская часть — socket, connect, send, recv, shutdown, closesocket.
Рассмотрим на примере реального приложения, как же происходит работа с сокетами Hyper-V. Интерфейсы взаимодействия очень похожи на обычные сетевые сокеты, отличаются лишь детали реализации.
В целом логика взаимодействия (с точки зрения взаимодействия с ядром) выглядит следующим образом.
Разберем основные функции на примере приложения. Какие-то отличия по одинаковым вызовам отметим по ходу разбора, а некоторые особенности клиентской части будут отражены далее при разборе PowerShell Direct.
В сокетах Hyper-V нет IP-адресов, зато есть заранее определенные GUID’ы.
В нашем случае мы возьмем HV_GUID_PARENT. Второй GUID, который нам понадобится, — это специально сгенерированный нами GUID для сервиса PowerShell. Для этого мы запускаем PowerShell-скрипт следующего содержания:
$friendlyName = "HV Socket Application"
# Create a new random GUID and add it to the services list then add the name as a value
$service = New-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\GuestCommunicationServices" -Name ((New-Guid).Guid)
$service.SetValue("ElementName", $friendlyName)
# Copy GUID to clipboard for later use
Write-Host "Service GUID: " $service.PSChildName
и запоминаем полученный GUID. Но, в принципе, можно использовать существующие GUID’ы, которые уже созданы на этапе установки Windows в том же разделе реестра:
HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\GuestCommunicationServices
VM Session Service 1 и VM Session Service 2 используются для работы PowerShell Direct (второй GUID — до внедрения механизма Hyper-V socket duplication. Если в рамках одной и той же PowerShell-сессии с помощью New-PSSession открывается два соединения, то используются два GUID’а).
Если попытаться открыть три соединения сразу, то встретишь ошибку.
и откроется всего две сессии. При отправке сообщений гостевой ОС мы можем увидеть оба GUID’а.
Но у нас будет всего один канал для коммуникации, и, соответственно, необходим один GUID: B1D00D3E-FE10-4570-AD62-7648779D7A1B
.
int iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
Вызов WSAStartup пропустим, так как он не имеет специфических параметров для работы с сокетами Hyper-V, перейдем сразу к функции socket.
Socket
Каким образом происходит заполнение параметров, посмотри в исходном коде приложения ServerExample, а мы перейдем непосредственно к вызову соответствующей API-функции.
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_HYPERV;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = HV_PROTOCOL_RAW;
ListenSocket = socket(hints.ai_family, hints.ai_socktype, hints.ai_protocol);
Код скомпилируется в следующее.
Из ws2_32!WSASocketA
вызывается ws2_32!WSASocketW
, из которой затем вызывается ws2_32!DPROVIDER__Initialize
.
__WINDBG>dc poi(esp+4) L100 — второй параметр функции DPROVIDER::Initialize (значение реестра Protocol_Catalog9\Catalog_Entries\000000000001)__
0122b48c 00020026 00000000 00000000 00000000 &...............
0122b49c 00000008 1234191b 4ca74bf7 d7dfe086 ......4..K.L....
0122b4ac 45542bc3 000003e9 00000001 00000000 .+TE............
0122b4bc 00000000 00000000 00000000 00000000 ................
0122b4cc 00000000 00000000 00000002 00000022 ............"...
0122b4dc 00000024 00000024 00000001 00000001 $...$...........
0122b4ec 00000000 00000000 00000000 00000000 ................
0122b4fc 00000000 00790048 00650070 002d0072 ....H.y.p.e.r.-.
0122b50c 00200056 00410052 00000057 00000000 V. .R.A.W.......
0122b51c 00000000 00000000 00000000 00000000 ................
Далее видим инициализацию указателей на вспомогательные функции.
В целом в usermode выполняется достаточное количество операций, но мы укажем только самые важные.
Далее с помощью LoadLibraryEx
загружается mswsock.dll
, затем GetProcAddress
возвращает адрес функции mswsock!WSPStartup
, после чего выполнение передается на эту функцию. Внутри выполняются mswsock_initialize
, затем ws32SQMinit
, WahCreateContextTable
.
После завершения процедуры mswsock!WSPStartup
вызывается процедура mswsock!WSPSocket
(через call esi), из которой вызывается функция mswsock!socksocket
, а из нее mswsock!sockGetTdiName
, при этом первым параметром идет
__WINDBG>dtx _GUID poi(esp+4)__
(*((_GUID *)0xf6f4a0)) : {1234191B-4BF7-4CA7-86E0-DFD7C32B5445} [Type: _GUID]
__WINDBG> dc poi(esp+4)__
00f6f4a0 1234191b 4ca74bf7 d7dfe086 45542bc3 ..4..K.L.....+TE — GUID Hyper-V RAW (тот, что выводит netsh)
Затем вызывается mswsock!SockLoadTransportList
, которая считывает значение раздела реестра:
SYSTEM\CurrentControlSet\Services\Winsock\Parameters\Transports
Возвращаются следующие значения:
__WINDBG>dc @ebx — (в ebx указатель на блок памяти, переданный mswsock!SockLoadTransportList)__
01224ee0 006d0076 00750062 00000073 00730050 v.m.b.u.s...P.s.
01224ef0 00680063 00640065 00540000 00700063 c.h.e.d...T.c.p.
01224f00 00700069 00540000 00700063 00700069 i.p...T.c.p.i.p.
01224f10 00000036 abab0000 abababab feeeabab 6..............
Вызывается mswsock!SockLoadHelperDll
, запрашивается значение HKLM\System\CurrentControlSet\Services\vmbus\Parameters\Winsock\HelperDllName
, и загружается библиотека C:\Windows\SysWoW64\wshhyperv.dll
(приложение ServerExample скомпилировано как 32-битное).
При возврате из mswsock!SockGetTdiName
возвращается wshhyperv!WSHOpenSocket2
, которая содержит только проверки правильности передачи параметров сокета.
Далее последовательно GetCurrentProcess\OpenProcessToken
и затем GetTokenInformation
. Можем увидеть, что в качестве _TOKEN_INFORMATION_CLASS
передается 0x1D:
__WINDBG>dt _TOKEN_INFORMATION_CLASS @esp+8__
combase!_TOKEN_INFORMATION_CLASS
1d ( TokenIsAppContainer ) — похоже на адаптацию сокетов для приложений, скомпилированных с опцией \APPCONTAINER
После этого результат записывается в переменную mswsock!SockIsAppContainter. Видна инициализация строки \Device\Afd\Endpoint, которая передается ntdll!NtCreateFile
.
__WINDBG>dtx OBJECT_ATTRIBUTES poi(@esp+0x8) (третий параметр ntdll!NtCreateFile)__
(*((OBJECT_ATTRIBUTES *)0xf6f348)) [Type: OBJECT_ATTRIBUTES]
[+0x000] Length : 0x18 [Type: unsigned long]
[+0x004] RootDirectory : 0x0 [Type: void *]
[+0x008] ObjectName : 0xf6f33c : "\Device\Afd\Endpoint" [Type: _UNICODE_STRING *]
[+0x00c] Attributes : 0x42 [Type: unsigned long]
[+0x010] SecurityDescriptor : 0x0 [Type: void *]
[+0x014] SecurityQualityOfService : 0x0 [Type: void *]
__WINDBG>dtx _IO_STATUS_BLOCK @esp+0xc -r (четвертый параметр ntdll!NtCreateFile)__
(*((_IO_STATUS_BLOCK *)0xf6f30c)) [Type: _IO_STATUS_BLOCK] — неинициализированная структура
[+0x000] Status : 16184168 [Type: long]
[+0x000] Pointer : 0xf6f368 [Type: void *]
[+0x004] Information : 0x0 [Type: unsigned long] — после выполнения будет возвращен статус операции (FILE_CREATED, FILE_OPENED, FILE_OVERWRITTEN, FILE_SUPERSEDED, FILE_EXISTS, FILE_DOES_NOT_EXIST)
Далее выполнение переходит в ядро драйвера afd.sys
. При инициализации этот драйвер регистрирует обработчики IRP:
__WINDBG>!drvobj afd 2__
Driver object (ffffda8527de19c0) is for:
\Driver\AFD
DriverEntry: fffff803db38a000 afd!GsDriverEntry
DriverStartIo: 00000000
DriverUnload: fffff803db34c380 afd!AfdUnload
AddDevice: 00000000
Dispatch routines:
[00] IRP_MJ_CREATE fffff803db357e90 afd!AfdDispatch
[01] IRP_MJ_CREATE_NAMED_PIPE fffff803db357e90 afd!AfdDispatch
[02] IRP_MJ_CLOSE fffff803db357e90 afd!AfdDispatch
[03] IRP_MJ_READ fffff803db357e90 afd!AfdDispatch
[04] IRP_MJ_WRITE fffff803db357e90 afd!AfdDispatch
__WINDBG>kn__
Child-SP RetAddr Call Site
00 afd!AfdDispatch
01 nt!IopParseDevice+0x1655
02 nt!ObpLookupObjectName+0x8b2
03 nt!ObOpenObjectByNameEx+0x1dd
04 nt!IopCreateFile+0x3d9
05 nt!NtCreateFile+0x79
06 nt!KiSystemServiceCopyEnd+0x13
07 ntdll!NtCreateFile+0x14
Соответственно, после вызова ntdll!NtCreateFile
попадем в Afd!AfdDispatch
. Первый параметр обработчика:
__WINDBG>!devobj @rcx__
Device object (ffffda8527de29d0) is for:
Afd \Driver\AFD DriverObject ffffda8527de19c0
Current Irp 00000000 RefCount 79 Type 00000011 Flags 00000050
Dacl ffffcb8a7e8ccd11 DevExt 00000000 DevObjExt ffffda8527de2b20
ExtensionFlags (0x00000800) DOE_DEFAULT_SD_PRESENT
Characteristics (0x00020000) FILE_DEVICE_ALLOW_APPCONTAINER_TRAVERSAL
Device queue is not busy.
__WINDBG>!devstack ffffda8527de29d0 — в стеке только одно устройство__
!DevObj !DrvObj !DevExt ObjectName
> ffffda8527de29d0 \Driver\AFD 00000000 Afd
Второй параметр Afd!AfdDispatch — это IRP.
__WINDBG>!irp @rdx__
Irp is active with 4 stacks 4 is current (= 0xffffda8529720378)
No Mdl: System buffer=ffffda8527088910: Thread ffffda8528714080: Irp stack trace.
cmd flg cl Device File Completion-Context
>[IRP_MJ_CREATE(0), N/A(0)]
0 0 ffffda8527de29d0 ffffda8528bc3550 00000000-00000000
\Driver\AFD
Args: ffff908026e7b5d0 03000020 00030000 00000039
Можем убедиться, что пакет отправлен приложением:
__WINDBG>!thread ffffda8528714080__
THREAD ffffda8528714080 Cid 0f74.0ca8 Teb: 0000000000ddf000 Win32Thread: ffffda85272b94e0 RUNNING on processor 0
IRP List:
ffffda85297201d0: (0006,0310) Flags: 00000884 Mdl: 00000000
Not impersonating
DeviceMap ffffca89863843b0
Owning Process ffffda8527121080 Image: ServerExample.exe
Attached Process N/A Image: N/A
Wait Start TickCount 416475 Ticks: 1 (0:00:00:00.015)
Context Switch Count 3006 IdealProcessor: 0
Далее вызывается Afd!AfdCreate. Первый параметр — все тот же IRP. Далее — Afd!AfdCheckTDIFilter
.
__WINDBG>r — параметры Afd!AfdCheckTDIFilter__
rcx=0000000000000001
rdx=0000000000000022 (Address Family - AF_HYPERV)
r8=0000000000000001
r9=0000000000000000
В ней производится поиск семейства адресов AF_HYPERV, который был передан в качестве параметра, в структуре AfdTdiMapping
(шесть элементов, размер элемента 20h байт). Структура содержит ссылки на стандартные сетевые устройства Windows:
\\Device\\Tcp
\\Device\\Tcp6
\\Device\\Udp
\\Device\\Udp6
\\Device\\RawIp
\\Device\\RawIp6
Ни одно из этих устройств не используется для AF_HYPERV. Возвращается указатель на структуру AfdTdiMapping
. Далее — Afd!AfdAllocateEndpoint
, из которой вызывается Afd!AfdTlFindAndReferenceTransport
.
rcx=0000000000000022 (Address Family - AF_HYPERV)
rdx=000000000000001
r8=0000000000000001
r9=0000000000000001
В этой функции идет работа со структурой AfdTlTransportListHead
. Она содержит связанный список указателей на объекты транспортов, инструкцией mov rbx,[rbx]
происходит загрузка следующего элемента, и выполняется сравнение семейства адресов AF_HYPERV (0x22) с [rbx+16h]; если совпадет, то функция вернет адрес структуры:
__WINDBG>dps @rax__
ffffda85`27f6b8c0 fffff803`db337530 afd!AfdTlTransportListHead
ffffda85`27f6b8c8 ffffda85`27dfeca0
ffffda85`27f6b8d0 00220000`00000006
ffffda85`27f6b8d8 00000001`00000001
ffffda85`27f6b8e0 ffffda85`27f6ba80
ffffda85`27f6b8e8 fffff803`db95c000 hvsocket!VmbusTlProviderDispatch
ffffda85`27f6b8f0 11d49b1a`eb004a27
ffffda85`27f6b8f8 bc597704`50002391
ffffda85`27f6b900 ffffda85`27f6b96c
ffffda85`27f6b908 00000000`00000000
ffffda85`27f6b910 62524d4e`02080006
ffffda85`27f6b918 8ab20db4`3a180386
__WINDBG>dt _GUID @rax+30h — содержит GUID NPI_MS_VMBUS_MODULEID__
ServerExample!_GUID
{eb004a27-9b1a-11d4-9123-0050047759bc}
Далее вызывается afd!PplGenericAllocationFunction
(выделение необходимой памяти), после заполнения необходимых структур вызывается nt!NtAllocatePoolEx
. Затем вызывается nt!ObjDerefernceObject
, при этом в rcx загружается указатель на процесс (ServerExample.exe).
__WINDBG>!object @rcx__
Object: ffffda8527121080 Type: (ffffda8527096f20) Process
ObjectHeader: ffffda8527121050 (new version)
HandleCount: 8 PointerCount: 207781
Далее увеличивается на единицу глобальная переменная AfdEndpointsOpened
. На момент отладки
__WINDBG>dd afd!AfdEndpointsOpened L4__
fffff803`db3378e8 000009af 00000000 00000000 00000000
Идет проверка AfdEndpointListHead
, не пустая ли она.
Затем в эту структуру вставляется новый элемент
AfdEndpointListHead
, как мы видели ранее, содержит созданные объекты сокетов.
В принципе, основная функция afd!AfdAllocateEndpoint заключается в создании нового элемента типа _AFD_CONNECTION и его добавлении в массив AfdEndpointListHead. Затем меняется состояние сокета (записывается 0AFD).
Константы AFD, AFD1, AFD2, AFD4, AFD8, AAFD и подобные служат индикаторами состояния соединения. Но похоже, что полного соответствия с RFC 793, где описываются возможные состояния сокетов (LISTEN, SYN-SENT, SYN-RECEIVED, ESTABLISHED, FIN-WAIT-1, FIN-WAIT-2, CLOSE-WAIT, CLOSING, LAST-ACK, TIME-WAIT), нет. Также байт слева от константы меняется в зависимости от типа вызова 0001afd0. Например, после вызова bind его значение станет равно трем.
__WINDBG>dc @rdi — указатель на структуру _AFD_CONNECTION__
ffffda85`2943e9e0 0001afd0 00000000 00000100 00000000 ................
ffffda85`2943e9f0 00000000 00000000 00000000 00000000 ................
ffffda85`2943ea00 00000000 00000000 27121080 ffffda85 ...........'....
Далее вызывается hvsocket!VmbusTlEndpointIsPrivileged
, затем afd!AfdTLCreateEndpoint
__WINDBG>r__
rcx= ffffda852943e9e0 — указатель на объект _AFD_CONNECTION, с которым производится работа
rdx=0000000000000022 — Address Family
r8=0000000000000001 — Hyper-V RAW
r9=ffffda8527f6b8c0 — указатель на один из элементов AfdTlTransportListHead
В этой функции в стеке обнуляются 50h байт и затем размещаются параметры для вызываемой функции hvsocket!VmbusTlProviderEndpoint
:
__WINDBG>dps @rsp+20__
ffff9080`26e7b250 fffff803`db362930 afd!AfdTLCreateEndpointComplete
ffff9080`26e7b258 ffffda85`297201d0 — IRP
ffff9080`26e7b260 00000000`00000000
ffff9080`26e7b268 00000001`00010022
ffff9080`26e7b270 ffffda85`2943e9e0 — указатель на _AFD_CONNECTION struct
ffff9080`26e7b278 ffffda85`27121080 — Process Object (it not really need. In hvsocket if it eq zero driver calls PsGetCurrentProcess)
ffff9080`26e7b280 ffffda85`28714080 — pointer to THREAD structure of ServerExmple.exe process (if it zero later will be called ndis!NdisGetProcessObjectCompartmentId before in rcx load Process struct)
ffff9080`26e7b288 ffffca89`8568e280
ffff9080`26e7b290 00000000`00000000
ffff9080`26e7b298 00000000`00000000
Затем в rax загружается указатель на hvsocket!VmBusTlProviderEndpoint
, в r14 наш IRP:
__WINDBG>!irp @r14__
Irp is active with 4 stacks 4 is current (= 0xffffdf08c0c5b408)
>[IRP_MJ_CREATE(0), N/A(0)]
0 0 ffffdf08bf161920 ffffdf08c0eb5780 00000000-00000000
\Driver\AFD
Args: ffffa700e57f35d0 03000020 00030000 00000039
Выполняется вызов этой функции. Выполняются проверки структуры, переданной в rax, затем для объекта ETHREAD вызывается ndis!NdisGetThreadObjectCompartmentId
, затем vmbus!VmbusTlCreateEndpoint
(второй параметр — указатель на объект EPROCESS), далее hvsocket!VmbusTlCreateObjectFromLookasideList
, в которой вызывается nt!ExpInterlockedPopEntrySList.
Затем hvsocket!VmbusTlInitializeObject
(вызывает nt!KeInitializeEvent
и nt!KeInitializeSpinLock
). Происходит возврат из hvsocket!VmbusTlCreateObjectFromLookasideList
, выделяется блок памяти размером 200h, тег Vnpi. Далее обнуляется блок памяти размером 38h, вызываются nt!KeEnterCriticalRegion
и nt!ExAcquireFastMutexUnsafe
и выполняется регистрация hvsocket!VmbusTlEndpointActionWorkQueueRoutine
через netio!NetioInitializeWorkQueue
.
После происходит возврат из vmbus!VmbusTlCreateEndpoint
, затем вызывается afd!AfdTLCreateEndpointComplete(PIRP IRP)
, четвертый параметр — vmbus!VmbusTlProviderEndpointDispatch
, в структуру _AFD_CONNECTION
записывается адрес hvsocket!VmbusTlCreateEndpoint
. Затем вызывается afd!ObDereferenceSecurityDescriptor
, идет проверка успешного результата — в зависимости от этого либо выполняется nt!iofCompleteRequest
, либо нет, — и происходит возврат из hvsocket.sys
в afd.sys
.
Значение, возвращенное vmbus!VmbusTlProviderEndpoint
, — 103h. Поэтому далее вызывается AfdTLPendRequest
, если результат выполнения не 103h, но сразу будет вызов afd!AfdCompleteTLEndpCreate
, и только после этого идет afd!AfdCompleteTLEndpCreate
и nt!IofComplheteRequest
. Происходит возврат из afd!AfdCreate
(в rax все тот же 103h).
__WINDBG>dc 0xf6f368 — после выполнения NtCreateFile__
00f6f368 00000000 00000000 00f6f498 00000003 ................
00f6f378 00000144 00000000 00000000 00000022 D..........."...
00f6f388 00000000 00000144 c0140000 00000020 ....D....... ...
00f6f398 80000000 00000001 00000039 00000022 ........9..."...
0x144 — это тот самый handle, который в конечном итоге вернет функция socket и который будет передан функции bind в качестве первого параметра. Этот handle создается с помощью функции nt!ObpCreateHandle
, вызываемой из функции nt!ObOpenObjectByNameEx
(ранее при выполнении NtCreateFile):
__WINDBG>k__
Child-SP RetAddr Call Site
ffffd201`d9b6e820 fffff801`a72630e9 nt!ObOpenObjectByNameEx+0x310
ffffd201`d9b6e960 fffff801`a7262cf9 nt!IopCreateFile+0x3d9
ffffd201`d9b6ea00 fffff801`a6fd4493 nt!NtCreateFile+0x79
ffffd201`d9b6ea90 00007ff9`e3f46b74 nt!KiSystemServiceCopyEnd+0x13
00000000`00e3e0a8 00000000`58f9ae28 ntdll!NtCreateFile+0x14
До выполнения функции в списке объектов:
__!handle 0 f ffffda8527121080__
013c: Object: ffffca8986a9e3d0 GrantedAccess: 00020019 (Inherit) Entry: ffffca8985cb14f0
Object: ffffca8986a9e3d0 Type: (ffffda852717d0e0) Key
ObjectHeader: ffffca8986a9e3a0 (new version)
HandleCount: 1 PointerCount: 32759
Directory Object: 00000000 Name: \REGISTRY\MACHINE\SYSTEM\SYSTEM\CONTROLSET001\SERVICES\WINSOCK2\PARAMETERS\NAMESPACE_CATALOG5
0140: Object: ffffda85299be6f0 GrantedAccess: 001f0003 (Audit) Entry: ffffca8985cb1500
Object: ffffda85299be6f0 Type: (ffffda8527087650) Event
ObjectHeader: ffffda85299be6c0 (new version)
HandleCount: 1 PointerCount: 1
0144: free handle, Entry address ffffca8985cb1510, Next Entry ffffca8985cb1520
0148: free handle, Entry address ffffca8985cb1520, Next Entry ffffca8985cb1530
После выполнения функции появляется новая запись:
__!handle 0 f 0xffffe382ff528480 — ServerExample.exe process object__
0144: Object: ffffda8529a9bcf0 GrantedAccess: 0016019f (Audit) Entry: ffffca8985cb1510
Object: ffffda8529a9bcf0 Type: (ffffda852718cb00) File
ObjectHeader: ffffda8529a9bcc0 (new version)
HandleCount: 1 PointerCount: 2
Directory Object: 00000000 Name: \Endpoint {Afd}
0148: free handle, Entry address ffffca8985cb1520, Next Entry ffffca8985cb1530
Инструкцией mov rax, [rpb-58h]
в rax кладется значение дескриптора.
Возвращаемся в usermode. Далее идет вызов mswsock!SockGetInformation
(из нее происходит вызов ntdll!NtDeviceIoControlFile
, которой в качестве handle файла передается возвращенный ранее 0x144).
NTSTATUS WINAPI NtDeviceIoControlFile(
_In_ HANDLE FileHandle, - 144 (\Device\Afd\Endpoint). На клиенте будет \Device\Afd
_In_ HANDLE Event, - 140 (Event)
_In_ PIO_APC_ROUTINE ApcRoutine, 0
_In_ PVOID ApcContext, 0
_Out_ PIO_STATUS_BLOCK IoStatusBlock, F6F2E0
_In_ ULONG IoControlCode, 1207B - AfdDispatchImmediateIrp ()
_In_ PVOID InputBuffer, F6F2FC
_In_ ULONG InputBufferLength,10
_Out_ PVOID OutputBuffer, F6F2FC
_In_ ULONG OutputBufferLength 10
);
Input buffer:
__WINDBG>dc F6F2FC__
00f6f2fc 00000007 00f6f38c c0140000 00f6f348 ............H...
IOCTL-код — 1207Bh (afd!AfdDispatchImmediateIrp
), но она не выполнится, так как задействован механизм FastIO (подробности далее на примере send), выполнится afd!AfdFastIoDeviceControl
, при этом IRP-пакет не формируется, usermode-буфер и его длина передаются как третий и четвертый параметр этой функции:
__WINDBG>r__
rcx — объект \Endpoint {Afd}
r8=0000000000f6f2fc
r9=0000000000000010
После выполнения функции (результат записывается в тот же буфер):
__WINDBG>dc F6F2FC__
00f6f2fc 00000007 00000000 00010000 00000000 ................
Далее функция ntdll!NtDeviceIoControlFile
вызывается повторно, в input-буфере данные, которые были возвращены после предыдущего выполнения. Но после повтора результат не изменился. Если бы функция вернула 103h, то вызвалась бы mswsock!SockWaitForSingleObject
, затем ws2_32!WahInsertHandleContext
, далее выход из mswsock!SockSocket
и возврат в mswsock!WSPSocket
. Вход в mswsock!SockSetHandleContext
. Далее вызывается wshhyperv!WSHGetSocketInformation
, затем nt!NtDeviceIoControlFile
с IOCTL 12047h (AfdDispatchImmediateIrp
).
В качестве input-буфера передается (размер буфера — D4):
__WINDBG>dc 006FED58 006FED58+D4__
00f6f358 00000000 00000022 00000001 00000001 ...."...........
00f6f368 00000024 00000024 00000000 00000000 $...$...........
00f6f378 00000000 00010000 00010000 00001000 ................
00f6f388 00000000 000003e9 00020026 00000008 ........&.......
00f6f398 00000000 00000000 00000000 00000000 ................
00f6f3a8 00000000 00000000 00000000 00000000 ................
00f6f3b8 00000000 00000000 1234191b 4ca74bf7 ..........4..K.L
00f6f3c8 d7dfe086 45542bc3 00000004 656b6361 .....+TE....acke
00f6f3d8 00000000 00000000 00000000 00000000 ................
00f6f3e8 00000000 00000000 00000000 00000000 ................
00f6f3f8 00000000 012247f0 00000000 00000000 .....G".........
00f6f408 00000000 00000000 00000000 00000000 ................
00f6f418 00000000 00000000 00000000 d2ffd7d3 ................
00f6f428 00000022
Возвращается (изменений после выполнения не произошло):
__WINDBG>dc 00F6F400 00F6F400+24__
00f6f400 00000000 00000000 00000000 00000000 ................
00f6f410 00000000 00000000 00000000 00000000 ................
00f6f420 00000000 d2ffd7d3 ........
Оттуда идет вызов ws2_32!WPUModifyIFSHandle
(вызывается ws2_32!WahInsertHandleContext
, идет работа с массивом ws2_32!SockPrimes
). Выход из mswsock!SockSetHandleContext
. Возврат в приложение.
Bind
Функция socket завершилась успешно, дальше выполняется bind:
iResult = bind(ListenSocket, hints.ai_addr, (int)hints.ai_addrlen);
Дальше usermode так подробно, как для socket, разбирать мы не будем, отметим только те моменты, которые специфичны именно для сокетов Hyper-V. Вызывается mswsock!WSPbind
, далее mswsock!WahReferenceContextByHandle
(HANDLE socket, PVOID SockContextTable). Адрес процедуры, содержащийся в ebx, сравнивается с адресом начала mswsock_Tcpip4_WSHGetSockaddrType либо mswsock_Tcpip6_WSHGetSockaddrType. Если адрес совпадает, то вызывается соответствующая процедура, если нет, то выполняется call ebx
(в нашем случае wshhyperv!WSHGetSockaddrType
).
Тип сокета вычисляется на основании GUID’а, заданного во время выполнения bind (у нас задан HV_GUID_ZERO).
На сервере в esi:
__WINDBG>dc @esi__
00f6f990 00000022 00000000 00000000 00000000 "............... — HV_GUID_ZERO
00f6f9a0 00000000 b1d00d3e 4570fe10 487662ad ....>.....pE.bvH
00f6f9b0 1b7a9d77
На клиенте:
__WINDBG>dc esi — при этом в esi virtual machine ID GUID -6a964317-1d87-4a74-abf9-46a69b048900 (GUID, который возвращается на сервере при выполнении командлета Get-VM | select ID, Name)__
00affb2c 00000022 6a964317 4a741d87 a646f9ab "....C.j..tJ..F.
00affb3c 0089049b b1d00d3e 4570fe10 487662ad ....>.....pE.bvH
00affb4c 1b7a9d77 6a964317 4a741d87 a646f9ab w.z..C.j..tJ..F. ????????????
00affb5c 0089049b b1d00d3e 4570fe10 487662ad ....>.....pE.bvH
00affb6c 1b7a9d77 00d90000 40000062 02020202 w.z.....b..@....
00affb7c 536e6957 206b636f 00302e32 00000000 WinSock 2.0.....
Сравнение GUID производится сначала с HV_GUID_LOOPBACK, затем с HV_GUID_BROADCAST и потом с HV_GUID_ZERO. После возвращается 0. Перед этим была проверка второго аргумента на превышение 24 и первого dword 1 аргумента на равенство с 22. В противном случае возвращалась ошибка (271E и 273F соответственно).
Далее идут различные проверки, затем вызывается ntdll!NtDeviceIoControlFile
:
NTSTATUS WINAPI NtDeviceIoControlFile(
_In_ HANDLE FileHandle, - 144 (\Device\Afd\Endpoint). На клиенте будет \Device\Afd
_In_ HANDLE Event, - 140 (Event)
_In_ PIO_APC_ROUTINE ApcRoutine, 0
_In_ PVOID ApcContext, 0
_Out_ PIO_STATUS_BLOCK IoStatusBlock, F6F728
_In_ ULONG IoControlCode, 12003
_In_ PVOID InputBuffer, 1223088
_In_ ULONG InputBufferLength,28
_Out_ PVOID OutputBuffer, 1223088
_In_ ULONG OutputBufferLength 24
);
Для наглядности проверим с помощью утилиты из комплекта Sysinternals Suite
handle.exe -a -p ServerExample.exe
140: Event
144: File (---) \Device\Afd\Endpoint (На клиенте будет \Device\Afd)
__WINDBG>dc 1223088 1223088+28__
01223088 00000000 00000022 00000000 00000000 ...."...........
01223098 00000000 00000000 b1d00d3e 4570fe10 ........>.....pE — GUID нашего сервиса VMSession
012230a8 487662ad 1b7a9d77 abababab .bvHw.z.....
Драйвер afd.sys
регистрирует свой обработчик IOCTL-кодов.
__0: kd> !drvobj afd 2__
Driver object (ffffa40eb952c0d0) is for:
\Driver\AFD
Dispatch routines:
[0e] IRP_MJ_DEVICE_CONTROL fffff8008ab7d460 afd!AfdDispatchDeviceControl
Fast I/O routines:
FastIoRead fffff80aa54fbed0 afd!AfdFastIoRead
FastIoWrite fffff80aa54fbfd0 afd!AfdFastIoWrite
FastIoUnlockAll fffff80aa55018f0 afd!AfdSanFastUnlockAll
FastIoDeviceControl fffff80aa54f1ab0 afd!AfdFastIoDeviceControl
Рассмотрим, что происходит в ядре на этом этапе. IOCTL-коду 12003 из таблицы AfdIoctlTable в таблице AfdIrpCallDispatch соответствует afd!AfdBind
, которая вызывается из afd!AfdDispatchDeviceControl
.
Функции afd!AfdBind
в rcx передается указатель на IRP.
Сперва происходит проверка размеров входящих и исходящих буферов (24 и 28) и других параметров. Выделяется пул размером 0x24, и в него перемещаются передаваемые параметры из usermode.
__WINDBG>dc @rdx — параметр memmove__
00000000`0122308c 00000022 00000000 00000000 00000000 "...............
00000000`0122309c 00000000 b1d00d3e 4570fe10 487662ad ....>.....pE.bvH
00000000`012230ac 1b7a9d77
С клиентской стороны буфер будет выглядеть так:
__WINDBG>dc @rdx__
00000000`013d38fc 00000022 6a964317 4a741d87 a646f9ab "....C.j..tJ..F.
00000000`013d390c 0089049b b1d00d3e 4570fe10 487662ad ....>.....pE.bvH
00000000`013d391c 1b7a9d77
Далее идет вызов nt!IoAllocateMdl
(Length 0x24).
__WINDBG>dt nt!_MDL @rax — результат выполнения IoAllocateMdl__
+0x000 Next : (null)
+0x008 Size : 0n56
+0x00a MdlFlags : 0n8
+0x00c AllocationProcessorNumber : 0
+0x00e Reserved : 0xffff
+0x010 Process : 0xffffda85`293c9840 _EPROCESS
+0x018 MappedSystemVa : 0xffff9080`27c4903c Void
+0x020 StartVa : 0x00000000`01223000 Void
+0x028 ByteCount : 0x24
+0x02c ByteOffset : 0x88
В rcx адрес UserBuffer из параметров DeviceIoControl
, затем загрузка страниц из файла подкачки (если они успели туда попасть) и их блокировка в памяти с помощью nt!MmProbeAndLockPages
. Для выделенного пула размером 0x24 устанавливается тип кеширования cached с помощью nt!MmMapLockedPagesSpecifyCache
, а также опции HighPagePriority и MdlMappingNoExecute
.
Afd!AfdTLBindSecurity
-> afd!AfdTLBind
-> afd!AfdTLIoControl
(из последней выходим в afd!AfdTLBindComplete
сразу после вызова afd!AfdTLBindComplete2
, только затем возвращаемся непосредственно на выход из afd!AfdTLIoControl
). Вызываем afd!AfdTLBindSecurity
(первый параметр — все тот же IRP, второй — объект _AFD_CONNECTION). Далее afd!AfdTlBind
(параметры те же). В функции afd!AfdTLBind
.
В rcx при этом загрузилось hvsocket!VmbusTlProviderEndpointDispatch
.
__WINDBG>dps @rcx L50 — таблица обработчиков из hvsocket.sys__
fffff803`db95c048 fffff803`db952460 hvsocket!VmbusTlCommonProviderCloseEndpoint
fffff803`db95c050 fffff803`db963210 hvsocket!VmbusTlEndpointIoControlEndpoint
fffff803`db95c058 fffff803`db95a8c0 hvsocket!TlDefaultRequestQueryDispatchEndpoint
fffff803`db95c060 fffff803`db952460 hvsocket!VmbusTlCommonProviderCloseEndpoint
fffff803`db95c068 fffff803`db963be0 hvsocket!VmbusTlListenerIoControlEndpoint
fffff803`db95c070 fffff803`db95a8c0 hvsocket!TlDefaultRequestQueryDispatchEndpoint
fffff803`db95c078 fffff803`db95a8d0 hvsocket!TlDefaultRequestResume
fffff803`db95c080 fffff803`db954300 hvsocket!VmbusTlConnectionCloseEndpoint
fffff803`db95c088 fffff803`db965fc0 hvsocket!VmbusTlConnectionIoControlEndpoint
fffff803`db95c090 fffff803`db95a8c0 hvsocket!TlDefaultRequestQueryDispatchEndpoint
fffff803`db95c098 fffff803`db954c60 hvsocket!VmbusTlConnectionSend
fffff803`db95c0a0 fffff803`db954ea0 hvsocket!VmbusTlConnectionReceive
fffff803`db95c0a8 fffff803`db9585a0 hvsocket!VmbusTlConnectionDisconnect
fffff803`db95c0b0 00000001`00000000
fffff803`db95c0b8 00000000`00000000
fffff803`db95c0c0 fffff803`db969de0 hvsocket!VmbusTlXPartAcceptConnection
fffff803`db95c0c8 00000000`00000000
fffff803`db95c0d0 fffff803`db959830 hvsocket!VmbusTlXPartProcessIoRequest
fffff803`db95c0d8 00000000`00000000
fffff803`db95c0e0 fffff803`db959890 hvsocket!VmbusTlXPartIoRequestCompleted
fffff803`db95c0e8 fffff803`db959950 hvsocket!VmbusTlXPartReleaseReceiveIndications
fffff803`db95c0f0 fffff803`db969ee0 hvsocket!VmbusTlXPartDisconnect
fffff803`db95c0f8 fffff803`db959820 hvsocket!VmbusTlXPartSendConsumptionNotice
fffff803`db95c100 fffff803`db959940 hvsocket!VmbusTlXPartIsIncomingEmpty
fffff803`db95c108 00000000`00000000
fffff803`db95c110 00000001`00000000
fffff803`db95c118 fffff803`db96a430 hvsocket!VmbusTlLoopbackSetupConnection
fffff803`db95c120 fffff803`db96a6b0 hvsocket!VmbusTlLoopbackAcceptConnection
fffff803`db95c128 00000000`00000000
fffff803`db95c130 fffff803`db959ed0 hvsocket!VmbusTlLoopbackProcessIoRequest
fffff803`db95c138 fffff803`db96a850 hvsocket!VmbusTlLoopbackPostprocessIoRequest
fffff803`db95c140 fffff803`db959f10 hvsocket!VmbusTlLoopbackIoRequestCompleted
fffff803`db95c148 fffff803`db959f30 hvsocket!VmbusTlLoopbackReleaseReceiveIndications
fffff803`db95c150 fffff803`db95a0c0 hvsocket!VmbusTlLoopbackDisconnect
fffff803`db95c158 fffff803`db95a340 hvsocket!VmbusTlLoopbackNotifyReceiveConsumed
fffff803`db95c160 fffff803`db95a2f0 hvsocket!VmbusTlLoopbackIsIncomingEmpty
Затем вызывается afd!AfdTLIoControl
(перед вызовом формируется достаточно большой набор параметров), первым параметром идет указатель на функцию hvsocket!VmbusTlEndpointIoControlEndpoint
, которая затем и выполняется. Затем вызывается hvsocket!VmbusTlHandleEndpointIoControl
. В ходе выполнения получаем рекурсию:
__WINDBG>kc__
Call Site
afd!AfdTLIoControl
afd!AfdTLBindComplete2
afd!AfdTLBindComplete
afd!AfdTLIoControl
afd!AfdTLBind
afd!AfdTLBindSecurity
afd!AfdBind
nt!IopSynchronousServiceTail
На этот раз из hvsocket!VmbusTlHandleEndpointIoControl
вызывается hvsocket!VmbusTlContainerGetVmId
, а из нее hvsocket!VmbusTlFindAndReferencePartitionByContainerId
. В первом параметре функции по смещению +80h связный список:
__WINDBG>!list @rcx+80h__
ffffda85`27f6bb00 ffffda85`27f009e8 ffffda85`27f009e8
ffffda85`27f6bb10 00000000`00000001 ffffda85`27f6bb18
ffffda85`27f6bb20 ffffda85`27f6bb18 ffffda85`27f00910
ffffda85`27f6bb30 00000000`00000000 0000257a`d80d0eb8
ffffda85`27f6bb40 ffffda85`27f2de20 00000000`00000000
ffffda85`27f6bb50 00000000`00000000 00000000`00000000
ffffda85`27f6bb60 00000000`00000000 00000000`00000000
ffffda85`27f6bb70 00000000`00000000 00000000`00000000
ffffda85`27f009e8 ffffda85`27f6bb00 ffffda85`27f6bb00
ffffda85`27f009f8 4f790d35`90db8b89 cdb7c80a`ea49e98c – HV_CHILD_GUID
ffffda85`27f00a08 00000000`00000000 ffffda85`27f00a10
ffffda85`27f00a18 00000000`00000000 00000000`00000000
ffffda85`27f00a28 00000000`00000000 00000000`00000000
ffffda85`27f00a38 00000000`00000000 00000000`00000000
ffffda85`27f00a48 00000000`00000000 00000000`00000000
ffffda85`27f00a58 fffff803`db9516c0 fffff803`db951db0
Если включить гостевую виртуальную машину, то появится еще один элемент:
ffffb08b`93e490e8 ffffb08b`923a9700 ffffb08b`924390e8
ffffb08b`93e490f8 4a741d87`6a964317 0089049b`a646f9ab — GUID включенной виртуальной машины, где запустится клиентская часть
ffffb08b`93e49108 00000000`00000001 ffffb08b`93e49110
ffffb08b`93e49118 00000000`00000000 00000000`00000000
ffffb08b`93e49128 00000000`00000000 00000000`00000000
ffffb08b`93e49138 00000000`00000000 00000000`00000000
ffffb08b`93e49148 00000000`00000000 00000000`00000000
ffffb08b`93e49158 fffff805`2dfc16c0 fffff805`2dfc1db0
Функция производит поиск по этому связному списку и возвращает структуру, в которой по смещению +E8h содержится указатель на HV_GUID_CHILDREN, если второй параметр, переданный функции hvsocket!VmbusTlContainerGetVmId
, совпадает со значением, размещенным по смещению +18Ch этой структуры. Далее идет afd!AfdTLBindGetAddrComplete
.
Стек такой (мы все еще внутри afd!AfdTLBindComplete2
):
__WINDBG>kc__
Call Site
afd!AfdTLBindGetAddrComplete
afd!AfdTLIoControl
afd!AfdTLBindComplete2
afd!AfdTLBindComplete
afd!AfdTLIoControl
afd!AfdTLBind
afd!AfdTLBindSecurity
afd!AfdBind
В ней вызывается tdi!TdiCopyBufferToMdl
, а копируемый буфер выглядит следующим образом.
После копирования последовательно вызываются nt!MmUnlockPages
и nt!ioFreeMdl
, а затем nt!IofCompleteRequest
. Выполнение afd!AfdTlBindSecurity
завершается. В EAX снова принудительно записывается 103h. Как мы видели раньше, именно это значение вернет функция NtDeviceIoControlFile
. В этом случае дополнительно вызывается mswsock!SockWaitForSingleObject
.
В usermode, как и при выполнении socket, вызывается mswsock!SockSetHandleContext
, из которой снова вызывается NtDeviceIoControlFile
. На этот раз
NTSTATUS WINAPI NtDeviceIoControlFile(
_In_ HANDLE FileHandle, - 144 (\Device\Afd\Endpoint). На клиенте будет \Device\Afd
_In_ HANDLE Event, - 140 (Event)
_In_ PIO_APC_ROUTINE ApcRoutine, 0
_In_ PVOID ApcContext, 0
_Out_ PIO_STATUS_BLOCK IoStatusBlock, F6F600
_In_ ULONG IoControlCode, 12047 (afd!AfdDispatchImmediateIrp)
_In_ PVOID InputBuffer, F6F608
_In_ ULONG InputBufferLength,D4
_Out_ PVOID OutputBuffer, 0
_In_ ULONG OutputBufferLength 0
);
В input-буфере.
Обработка данных снова будет проходить через FastIo. В этом случае поиск функции, обрабатывающей значение, производится в таблице AfdImmediateCallDispatch
. В нашем случае обработчиком станет Afd!AfdSetContext
. Вначале отключается доставка всех APC текущему потоку с помощью nt!KeEnterGuardedRegion
, содержимое UserBuffer копируется в одно из полей структуры _AFD_CONNECTION. Функция завершается вызовом nt!KeLeaveGuardedRegion
. Возвращаемся в приложение.
Listen
Далее серверное приложение вызывает Listen. Usermode рассматривать особо не будем, сразу обратимся к функции afd!AfdStartListen
. Все так же вызов идет через ntdll!NtDeviceIoControlFile
.
NTSTATUS WINAPI NtDeviceIoControlFile(
_In_ HANDLE FileHandle, - 144 (\Device\Afd\Endpoint).
_In_ HANDLE Event, - 140 (Event)
_In_ PIO_APC_ROUTINE ApcRoutine, 0
_In_ PVOID ApcContext, 0
_Out_ PIO_STATUS_BLOCK IoStatusBlock, F6F798
_In_ ULONG IoControlCode, 1200B (afd!AfdStartListen)
_In_ PVOID InputBuffer, F6F7A0
_In_ ULONG InputBufferLength,C
_Out_ PVOID OutputBuffer, 0
_In_ ULONG OutputBufferLength 0
);
__WINDBG>dc F6F7A0__
00f6f7a0 00000100 7fffffff 00000000
В ядре InputBuffer сравнивается с AfdUserProbeAddress
, затем формируется SLIST_ENTRY. В структуру _AFD_CONNECTION в поле «тип» записывается 0AFD4. Вызывается afd!AfdRefTLBaseEndpoint
, затем afd!AfdTLListen
, откуда вызывается hvsocket!VmbusTlProviderListen
, затем hvsocket!VmbusTlCreateEndpoint
, откуда вызывается nt!PsChargeProcessPoolQuota
POOL_TYPE — 200h (NonPagedPoolNx), процесс текущий.
Затем hvsocket!VmbusTlCreateObjectFromLookasideList
, вызывается nt!ExAlocatePoolWithTag
для буфера размером 130h, тег Vnpi, затем hvsocket!VmbusTlInitializeObject
, затем выполняется инициализация Work queue (Netio!NetioInitializeWorkQueue) с параметром hvsocket!VmbusTlEndpointActionWorkQueueRoutine
, далее hvsocket!VmbusTlAssociateListenerToPartition
, откуда вызывается hvsocket!VmbusTlFindAndReferencePartitionByContainerId
:
__WINDBG>kc__
Call Site
hvsocket!VmbusTlAssociateListenerToPartition
hvsocket!VmbusTlProviderListen
afd!AfdTLListen
afd!AfdStartListen
nt!IopSynchronousServiceTail
nt!IopXxxControlFile
nt!NtDeviceIoControlFile
Затем вызывается hvsocket!VmbusTlResolvePartitionId
, hvsocket!VmbusTlGetPartitionListenerEndpoint
, откуда hvsocket!VmbusTlFindOrCreateService
, функция завершается (в rax — 0), и идет проверка того, с каким разделом мы работаем. Получаем HV_CHILDREN_GUID, снова вызываем hvsocket!VmbusTlFindOrCreateService
и еще раз — hvsocket!VmbusTlFindOrCreateService
, в r14 возвращается 0C0000225.
Далее идет вызов hvsocket!VmbusTlFindAndReferencePartition
, затем hvsocket!VmbusTlEndpointIsPrivileged
, из которой вызывается последовательность
call cs:__imp_SeCaptureSubjectContextEx
lea rcx, [rbp+SubjectSecurityContext]
call cs:__imp_SeLockSubjectContext
call cs:__imp_IoGetFileObjectGenericMapping
Функция возвращает
__WINDBG>dt _GENERIC_MAPPING @rax__
combase!_GENERIC_MAPPING
+0x000 GenericRead : 0x120089
+0x004 GenericWrite : 0x120116
+0x008 GenericExecute : 0x1200a0
+0x00c GenericAll : 0x1f01ff
Затем nt!SeAccessCheck
и nt!ObDereferenceSecurityDescriptor
. Результат функции hvsocket!VmbusTlEndpointIsPrivileged
при одной из трассировок:
__WINDBG>!error @rax__
Error code: (NTSTATUS) 0xc0000022 (3221225506) — {Access Denied} A process has requested access to an object, but has not been granted those access rights. (Правда, последний байт результата на выходе обнуляется.)
В hvsocket есть переменная hvsocket!VmbusTlEndpointSecurityDescriptor
__WINDBG>!sd hvsocket!VmbusTlEndpointSecurityDescriptor__
->Revision: 0x1
->Sbz1 : 0x0
->Control : 0x4
SE_DACL_PRESENT
->Owner : is NULL
->Group : is NULL
->Dacl :
->Dacl : ->AclRevision: 0x2
->Dacl : ->Sbz1 : 0x0
->Dacl : ->AclSize : 0x34
->Dacl : ->AceCount : 0x2
->Dacl : ->Sbz2 : 0x0
->Dacl : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl : ->Ace[0]: ->AceFlags: 0x3
->Dacl : ->Ace[0]: OBJECT_INHERIT_ACE
->Dacl : ->Ace[0]: CONTAINER_INHERIT_ACE
->Dacl : ->Ace[0]: ->AceSize: 0x14
->Dacl : ->Ace[0]: ->Mask : 0x000f003f
->Dacl : ->Ace[0]: ->SID: S-1-5-18
->Dacl : ->Ace[1]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl : ->Ace[1]: ->AceFlags: 0x3
->Dacl : ->Ace[1]: OBJECT_INHERIT_ACE
->Dacl : ->Ace[1]: CONTAINER_INHERIT_ACE
->Dacl : ->Ace[1]: ->AceSize: 0x18
->Dacl : ->Ace[1]: ->Mask : 0x000f003f
->Dacl : ->Ace[1]: ->SID: S-1-5-95-0
->Sacl : is NULL
hvsocket!VmbusTlEndpointIsPrivileged
возвращает c0000000, снова идет вызов hvsocket!VmbusTlFindOrCreateService
.
__WINDBG>kc__
Call Site
hvsocket!VmbusTlFindOrCreateService
hvsocket!VmbusTlAssociateListenerToPartition
hvsocket!VmbusTlProviderListen
afd!AfdTLListen
afd!AfdStartListen
nt!IopSynchronousServiceTail
nt!IopXxxControlFile
nt!NtDeviceIoControlFile
И только теперь вызывается hvsocket!VmbusTlCreateService
, состоящий из двух вызовов:
hvsocket!VmbusTlCreateObject
;hvsocket!VmbusTlInitializeObjectTable
.
Создается AVL-таблица.
VOID RtlInitializeGenericTableAvl(
_Out_ PRTL_AVL_TABLE Table,
_In_ PRTL_AVL_COMPARE_ROUTINE CompareRoutine, - hvsocket!VmbusTlCompareGuids
_In_ PRTL_AVL_ALLOCATE_ROUTINE AllocateRoutine, - hvsocket!VmbusTlAllocateForAvlTable
_In_ PRTL_AVL_FREE_ROUTINE FreeRoutine, - hvsocket!VmbusTlFreeForAvlTable
_In_opt_ PVOID TableContext
);
После инициализации таблицы получаем:
__WINDBG>dt nt!_RTL_AVL_TABLE ffffb904c25c9f60__
+0x000 BalancedRoot : _RTL_BALANCED_LINKS
+0x020 OrderedPointer : (null)
+0x028 WhichOrderedElement : 0
+0x02c NumberGenericTableElements : 0
+0x030 DepthOfTree : 0
+0x038 RestartKey : (null)
+0x040 DeleteCount : 0
+0x048 CompareRoutine : 0xfffff80a`6d6d16c0 _RTL_GENERIC_COMPARE_RESULTS hvsocket!VmbusTlCompareGuids+0
+0x050 AllocateRoutine : 0xfffff80a`6d6d1db0 void* hvsocket!VmbusTlAllocateForAvlTable+0
+0x058 FreeRoutine : 0xfffff80a`6d6d1dd0 void hvsocket!VmbusTlFreeForAvlTable+0
+0x060 TableContext : 0x00000000`694c6353 Void
Вызов hvsocket!VmbusTlFindOrCreateService
завершен, затем вызывается hvsocket!VmbusTlAssociateListenerToService
, а в ней hvsocket!VmbusTlInsertObjectToTable
.
После этого таблица выглядит так:
__WINDBG>dt nt!_RTL_AVL_TABLE ffffb904c25c9f60__
+0x000 BalancedRoot : _RTL_BALANCED_LINKS
+0x020 OrderedPointer : (null)
+0x028 WhichOrderedElement : 0
+0x02c NumberGenericTableElements : 1
+0x030 DepthOfTree : 1
+0x038 RestartKey : (null)
+0x040 DeleteCount : 0
Завершается hvsocket!VmbusTlAssociateListenerToPartition
, затем вызывается afd!AfdTLListenComplete
и hvsocket!VmbusTlCommonProviderCloseEndpoint
и hvsocket!VmbusTlQueueEndpointAction
, из которой вызывается netio!NetioInsertWorkQueue
(в качестве процедуры обработки передается hvsocket!VmbusTlEndpointActionWorkQueueRoutine)
, возвращаемся в afd!AfdTlListenComplete
, где происходит вызов afd!AfdDerefTLBaseEndpoint
и затем nt!IofCompleteRequest
.
Возвращаемся в hvsocket!VmbusTlProviderListen
, далее вызывается hvsocket!VmbusTlListenerProcessPendingIncomingConnection
, откуда вызывается hvsocket!VmbusTlGetPendingConnection
, затем — hvsocket_VmbusTlpGetPendingConnection
, возвращается 103h, выход из afd, возвращаемся в usermode.
Далее вызывается mswsock!SockSetHandleContext
. Содержимое UserBuffer для ntdll!NtDeviceIoControlFile
.
Возвращаемся в приложение, выполняется вызов accept.
Accept
Вызывается ws2_32!WSAAccept
, затем mswsock!WSPAccept
, затем NtDeviceIoControlFile
:
NTSTATUS WINAPI NtDeviceIoControlFile(
_In_ HANDLE FileHandle, - 144 (\Device\Afd\Endpoint).
_In_ HANDLE Event, - 140 (Event)
_In_ PIO_APC_ROUTINE ApcRoutine, 0
_In_ PVOID ApcContext, 0
_Out_ PIO_STATUS_BLOCK IoStatusBlock, F6F5D0
_In_ ULONG IoControlCode, 1200С (afd!AfdWaitForListen). Что интересно, IOCTL 12090 ссылается на ту же самую функцию
_In_ PVOID InputBuffer, 0
_In_ ULONG InputBufferLength,0
_Out_ PVOID OutputBuffer, F6F730
_In_ ULONG OutputBufferLength 28
);
__WINDBG>k__
Child-SP RetAddr Call Site
afd!AfdWaitForListen
nt!IopSynchronousServiceTail+0x1a0
nt!IopXxxControlFile+0x674
nt!NtDeviceIoControlFile+0x56
nt!KiSystemServiceCopyEnd+0x13
Input-буфер, как видно, отсутствует, только output. Управление переходит к afd!AfdWaitForListen
, вызывается afd!AfdGetUnacceptedConnection
. Первый параметр — _AFD_CONNECTION, выполняется сравнение элемента по смещению +50h с адресом самой структуры, возвращается 0. Далее в IRP->CancelRoutine записывается afd!AfdCancelWaitForListen
.
Другие функции не вызываются, выполнение в afd завершается, возврата из NtDeviceIoControlFile
непосредственно в приложение не происходит. Посмотрим, куда же передается управление. Все вызовы nt завершаются инструкцией sysret, которая передает управление в usermode по адресу, указанному в rcx.
__WINDBG>u @rcx__
ntdll!NtReadFile+0x14:
00007ff9`41586194 c3 ret
00007ff9`41586195 cd2e int 2Eh
00007ff9`41586197 c3 ret
00007ff9`41586198 0f1f840000000000 nop dword ptr [rax+rax]
ntdll!NtDeviceIoControlFile:
00007ff9`415861a0 4c8bd1 mov r10,rcx
00007ff9`415861a3 b807000000 mov eax,7
00007ff9`415861a8 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
00007ff9`415861b0 7503 jne ntdll!NtDeviceIoControlFile+0x15 (00007ff9`415861b5)
Инструкция всего одна, поэтому рассмотрим стек:
__WINDBG>dqs @r8__
00000000`0b8fbb98 00007ff9`3dfc3d34 — попадаем в недра SysWoW.dll.
Стек при этом выглядит так:
__WINDBG>dqs @r8 (в регистр rsp будет помещено значение r8 непосредственно перед выполнением swapgs, sysret)__
00000000`0b8fbb98 00007ff9`3dfc3d34 KERNELBASE!ReadFile+0x74
00000000`0b8fbba0 00000000`00000000
Содержимое стека меняется во время выполнения nt!KeWaitForSingleObject
-> nt!KiCommitThreadWait
-> nt!KiSwapThread
, вызываемой из nt!IopSynchronousServiceTail
.
__WINDBG>kcn__
\# Call Site
00 nt!IopSynchronousServiceTail
01 nt!NtReadFile
02 nt!KiSystemServiceCopyEnd
03 ntdll!NtReadFile
04 KERNELBASE!ReadFile
05 SHCORE!CFileStream::Read
06 windows_storage!CShellLink::_LoadFromStream
07 windows_storage!CShellLink::_LoadFromFile
08 windows_storage!CShellLink::Load
09 windows_storage!InitializeFileHandlerWithFile
0a windows_storage!CFileSysItemString::HandlerCreateInstance
0b windows_storage!CFSFolder::_BindHandler
0c windows_storage!CFSFolder::GetUIObjectOf
0d windows_storage!CShellItem::BindToHandler
0e SHELL32!CAppResolver::GetAppIDForShortcut
0f SHELL32!CAppResolver::GetAppIDForWindow
10 Explorer!CTaskBand::CResolveWindowTask::_ResolveWindowWorker
11 Explorer!CTaskBand::CResolveWindowTask::_ResolveWindow
12 Explorer!CTaskBand::CResolveWindowTask::InternalResumeRT
13 Explorer!CRunnableTask::Run
14 windows_storage!CShellTask::TT_Run
15 windows_storage!CShellTaskThread::ThreadProc
16 windows_storage!CShellTaskThread::s_ThreadProc
17 SHCORE!ExecuteWorkItemThreadProc
18 ntdll!RtlpTpWorkCallback
19 ntdll!TppWorkerThread
1a KERNEL32!BaseThreadInitThunk
1b ntdll!RtlUserThreadStart
Соответственно, возврат происходит в другой поток. Вероятно, это особенности WoW64.
Для 64-битного процесса все выполняется без сюрпризов, и мы возвращаемся в конец функции nt!NtDeviceIoControlFile
после выполнения sysret, из которой происходит возврат в mswsock!WSPAccept
, и выполнение программы останавливается внутри функции mswsock!SockWaitForSingleObject
.
Выполнение функции продолжится, когда на клиентской стороне будет выполнен вызов connect. Выполнение accept продолжается. Вновь вызывается mswsock!SockSocket
(ранее выполнялась при вызове socket). В ней — mswsock!SockGetTdiName
(тот же GUID Hyper-V RAW). В esi указатель на структуру размером не менее 0xCC байт. Переменная mswsock!SockTLNPIListenerCount
равна единице, вызовов GetCurrentProcess
не происходит, снова инициализируется \\Device\\Afd\\Endpoint
, и вновь вызов NtCreateFile
.
NTSTATUS NtCreateFile(
_Out_ PHANDLE FileHandle,- 00F6F4A4
_In_ ACCESS_MASK DesiredAccess, C0140000
_In_ POBJECT_ATTRIBUTES ObjectAttributes, 00F6F460
_Out_ PIO_STATUS_BLOCK IoStatusBlock,- 00F6F480
_In_opt_ PLARGE_INTEGER AllocationSize, - 00000000
_In_ ULONG FileAttributes,- 00000000
_In_ ULONG ShareAccess,- 3
_In_ ULONG CreateDisposition,-3
_In_ ULONG CreateOptions,0x20
_In_ PVOID EaBuffer, 00F6F4DC
_In_ ULONG EaLength , 39
);
__WINDBG>dtx _OBJECT_ATTRIBUTES 00F6F460__
(*((_OBJECT_ATTRIBUTES *)0xf6f460)) [Type: _OBJECT_ATTRIBUTES]
[+0x000] Length : 0x18 [Type: unsigned long]
[+0x004] RootDirectory : 0x0 [Type: void *]
[+0x008] ObjectName : 0xf6f454 : "\Device\Afd\Endpoint" [Type: _UNICODE_STRING *]
[+0x00c] Attributes : 0x42 [Type: unsigned long]
[+0x010] SecurityDescriptor : 0x0 [Type: void *]
[+0x014] SecurityQualityOfService : 0x0 [Type: void *]
В ядре аналогично: afd!AfdCreateFile
-> afd!AfdCheckTDIFilter
не вызывается, сразу идет afd!AfdAllocateEndpoint
, после выполнения afd!AfdTlFindAndReferenceTransport
в rax:
__WINDBG>!mex.foreachitem @rax -c__
ffffda8527f6b8c0
fffff803db337530
ffffda8527dfeeb0
ffffda8527dfedd0
ffffda8527dfeca0
Processed 5 items.
Nt!IoGetCurrentProcess
, afd!AfdEndpointsFreeing
сравнивается с 0xA, если больше или равно, то вызывается afd!AfdReuseEndpoint
и ExReleaseResourceAndLeaveCriticalRegion
.
Знакомый вызов zafd!AfdTLCreateEndpoint
, затем afd!AfdTLPendRequest
(все тот же 103), затем AfdCompleteTLEndpCreate
, возвращаемся из AfdCreate
, функция nt_SeClearLearningModeObjectInformation
, затем SeSetLearningModeObjectInformation
и после этого ObpCreateHandle
.
Номер дескриптора следующий: 0x148. Получаем два сокета (формально — объекты типа «файл»):
0144: Object: ffffda8529a9bcf0 GrantedAccess: 0016019f (Audit) Entry: ffffca8985cb1510
Object: ffffda8529a9bcf0 Type: (ffffda852718cb00) File
ObjectHeader: ffffda8529a9bcc0 (new version)
HandleCount: 1 PointerCount: 32761
Directory Object: 00000000 Name: \Endpoint {Afd}
0148: Object: ffffda8529acc6d0 GrantedAccess: 0016019f (Inherit) Entry: ffffca8985cb1520
Object: ffffda8529acc6d0 Type: (ffffda852718cb00) File
ObjectHeader: ffffda8529acc6a0 (new version)
HandleCount: 1 PointerCount: 2
Directory Object: 00000000 Name: \Endpoint {Afd}
После возврата видим наш дескриптор:
WINDBG>dc 00F6F4A4
00f6f4a4 00000148
Далее снова вызывается
NTSTATUS WINAPI NtDeviceIoControlFile(
_In_ HANDLE FileHandle, - 144 File (---) \Device\Afd)
_In_ HANDLE Event, - 140 (Event)
_In_ PIO_APC_ROUTINE ApcRoutine, 0
_In_ PVOID ApcContext, 0
_Out_ PIO_STATUS_BLOCK IoStatusBlock, 00F6F5D0
_In_ ULONG IoControlCode, 00012010 (afd!AfdAccept)
_In_ PVOID InputBuffer, 00F6F5D8
_In_ ULONG InputBufferLength,0xС
_Out_ PVOID OutputBuffer, 0
_In_ ULONG OutputBufferLength 0
);
WINDBG>dd 00F6F5D8
00f6f5d8 00000000 00000001 00000148 00000000
Переходим в afd!AfdAccept
: IoIs32bitProcess
, затем выполняется ObReferenceObjectByHandle
(в качестве Handle — 148), затем в rax загружается ссылка на AfdDeviceObject
(0FFFFDA8527DE29D0h):
WINDBG>!devobj 0FFFFDA8527DE29D0h
Device object (ffffda8527de29d0) is for:
Afd \Driver\AFD DriverObject ffffda8527de19c0
Current Irp 00000000 RefCount 85 Type 00000011 Flags 00000050
Dacl ffffcb8a7e8ccd11 DevExt 00000000 DevObjExt ffffda8527de2b20
ExtensionFlags (0x00000800) DOE_DEFAULT_SD_PRESENT
Characteristics (0x00020000) FILE_DEVICE_ALLOW_APPCONTAINER_TRAVERSAL
Device queue is not busy.
и поле по смещению +8h в нашей структуре _AFD_CONNECTION сравнивается с AfdDeviceObject. Идет вызов nt!KeAcquireInStackQueuedSpinLock
, затем afd!AfdGetReturnedConnection
. В rax функция возвращает:
__WINDBG>dc @rax__
ffffda85`298e7ac0 0002afd8 00061000 28bf3e40 ffffda85 ........@>.(....
ffffda85`298e7ad0 28fa1180 ffffda85 db95c080 fffff803 ...(............
ffffda85`298e7ae0 27121080 ffffda85 17d0e6d1 00000019 ...'............
Затем afd!AfdAcceptCore
(первый параметр — IRP)
__WINDBG>dd @rdx__
ffffda85`29b80860 0002afd0 01000000 00000100 00000000
ffffda85`29b80870 297cf380 ffffda85 db95c048 fffff803
__WINDBG>dd @r8__
ffffda85`298e7ac0 0002afd8 00061000 28bf3e40 ffffda85
ffffda85`298e7ad0 28fa1180 ffffda85 db95c080 fffff803
Внутри функции: KeAcquireInStackQueuedSpinLockAtDpcLevel
, затем afd!AfdSetupAcceptEndpoint
(вызывается только imp_ExFreePoolWithTag
), затем _KeReleaseInStackQueuedSpinLockFromDpcLevel
, возврат в afd!AfdAccept
: KeReleaseInStackQueuedSpinLock
, затем AfdDerefTLBaseEndpoint
и AfdTLCloseEndpoint
, откуда вызывается hvsocket! VmbusTlCommonProviderCloseEndpoint
, а из нее hvsocket!VmbusTlQueueEndpointAction
, которая ставит в очередь вызов hvsocket!VmbusTlEndpointActionWorkQueueRoutine
через netio! NetioInsertWorkQueue
. Затем функция hvsocket! VmbusTlCommonProviderCloseEndpoint
возвращает 103h. Затем вызывается afd!AfdTlDereferenceTransport
, в которой сперва проверяется значение переменной WskTdiTransport
(равное нулю в нашем случае). Может быть вызвана netio!NmrClientDetachProviderComplete
. Возвращаемся в afd!AfdAccept
, вызывается ObfDereferenceObject
и IofCompleteRequest
, выходим из afd.sys
.
Через какое-то время выполнится поставленная в очередь hvsocket!VmbusTlEndpointActionWorkQueueRoutine
. Из нее вызывается hvsocket!VmbusTlCommonEndpointCleanup
, из которой вызывается afd!AfdTLCloseEndpointComplete
(видим вызов DbgPrint при некотором условии с сообщением 'Failed to close TLI endpoint! Status=%lx, AFD endp=%p',0Ah,0 — в ядре Windows редко такое встретишь, обычно используется WPP). Но в нашем случае условие (второй параметр не равен нулю) не срабатывает, поэтому идем далее в afd!AfdDereferenceEndpointInline
, возвращаемся в hvsocket, затем выполняется hvsocket!VmbusTlEndpointDestructor
, а в ней
PsReturnPoolQuota
и ObfDereferenceObject
(для объекта процесс ServerExample.exe), идет работа с переменной VmbusProviderContext
:
__WINDBG>dps VmbusProviderContext__
fffff803`db95e0b8 ffffda85`27f6ba80
fffff803`db95e0c0 00000000`00000000
fffff803`db95e0c8 00000000`00000000
fffff803`db95e0d0 00000000`00000000
fffff803`db95e0d8 00000000`00000000
fffff803`db95e0e0 00000000`00040001
fffff803`db95e0e8 00000000`00000000
fffff803`db95e0f0 00000000`00000000
fffff803`db95e0f8 00000000`00000000
fffff803`db95e100 ffffda85`27f2c0b0
fffff803`db95e108 ffffda85`27f2c0b0
fffff803`db95e110 00000000`00000000
fffff803`db95e118 00000000`00000000
fffff803`db95e120 00000000`00000000
fffff803`db95e128 fffff803`db95c260 hvsocket!WPP_ThisDir_CTLGUID_HvSocketTraceGuid
fffff803`db95e130 00000000`00000000
__WINDBG>dps 0FFFFDA8527F6BA80__
ffffda85`27f6ba80 00000000`00000006
ffffda85`27f6ba88 00000000`00000009
ffffda85`27f6ba90 00000000`00000001
ffffda85`27f6ba98 00000000`00000000
ffffda85`27f6baa0 00000000`00000000
ffffda85`27f6baa8 00000000`00060001
ffffda85`27f6bab0 ffffda85`27f6bab0
ffffda85`27f6bab8 ffffda85`27f6bab0
ffffda85`27f6bac0 00000000`00000000
ffffda85`27f6bac8 00000000`00000000
ffffda85`27f6bad0 fffff803`db951e50 hvsocket!HvSocketProviderDestructor
ffffda85`27f6bad8 00000000`00000001
ffffda85`27f6bae0 ffffda85`27f6b9a0
ffffda85`27f6bae8 ffffda85`27f6b8c0
ffffda85`27f6baf0 fffff803`db32d160 afd!AfdTlClientDispatch
ffffda85`27f6baf8 00000000`00000001
В очередь через netio!NetioInsertWorkQueue
ставится hvsocket!VmbusTlEndpointWorkQueueDestructor
, возвращаемся из hvsocket!VmbusTlEndpointDestructor
в netio!NetioInsertWorkQueue
и далее в ядро Windows.
Тем временем мы вернулись в mswsock!WSAAccept
, идем далее, вызывается mswsock!SockNotifyHelperDll
. Переходим в mswsock!SockCoreAccept
.
Три раза вызывается wshhyperv!WSHGetSocketInformation
, затем wshhyperv!WSHGetSocketInformation
, затем mswsock!SockUpdateWindowSizes
.
Вызывается mswsock!SockGetTdiHandles
, из которого снова вызывается ntdll!NtDeviceIoControlFile
:
NTSTATUS WINAPI NtDeviceIoControlFile(
_In_ HANDLE FileHandle, - 148 File (---) \Device\Afd)
_In_ HANDLE Event, - 140 (Event)
_In_ PIO_APC_ROUTINE ApcRoutine, 0
_In_ PVOID ApcContext, 0
_Out_ PIO_STATUS_BLOCK IoStatusBlock, 00F6F424
_In_ ULONG IoControlCode, 00012037 (afd!AfdAccept)
_In_ PVOID InputBuffer, 00F6F5D8
_In_ ULONG InputBufferLength,0xС
_Out_ PVOID OutputBuffer, 0
_In_ ULONG OutputBufferLength 0
);
Connect
Сервер готов принимать входящие подключения, так что теперь рассмотрим вызов connect, выполняемый из клиента.
Из ClientExample!Connect идет вызов ws2_32!Prolog_v2
, затем ws2_32!WahReferenceContextByHandle
(в качестве дескриптора передается 144 — handle \Device\Afd). Далее вызывается mswsock!WSPConnect
, из которой идет вызов mswsock!SockDoConnect
, потом вызывается mswsock!SOCK_SQM_INFO_CAPTURE__NonCore_WSAConnect
, следом — wshhyperv!WSHGetWildcardSockaddr
(возвращает wshhyperv!HV_GUID_ZERO
), затем mswsock!WSPBind
, откуда вызывается wshhyperv!WSHGetSockaddrType
.
__WINDBG>k — странно видеть вызов bind из функции connect, но что есть, то есть__
ChildEBP RetAddr
0036f478 7203873c ntdll!NtDeviceIoControlFile
0036f530 7204dab6 mswsock!WSPBind+0x1cc
0036f5c0 7204e3af mswsock!SockDoConnect+0x2c0
0036f5dc 75de4d76 mswsock!WSPConnect+0x1f
0036f62c 002611df WS2_32!connect+0x86
В input-буфер до вызова NtDeviceIOControlFile
(с IOCTL 12003) из mswsock!WSPBind
.
__WINDBG>dc 007D4930 007D4930+28__
007d4930 00000002 baad0022 00000000 00000000
007d4940 00000000 00000000 00000000 00000000
007d4950 00000000 00000000
После вызова.
Возвращаемся из mswsock!WSPBind
.
Далее mswsock!SockDoConnectReal
, из которой вызывается ntdll!NtDeviceIoControlFile
.
NTSTATUS WINAPI NtDeviceIoControlFile(
_In_ HANDLE FileHandle, - 144 File (---) \Device\Afd)
_In_ HANDLE Event, - 140 (Event)
_In_ PIO_APC_ROUTINE ApcRoutine, 0
_In_ PVOID ApcContext, 0
_Out_ PIO_STATUS_BLOCK IoStatusBlock, 0036F4C0
_In_ ULONG IoControlCode, 00012007 (afd!AfdConnect)
_In_ PVOID InputBuffer, 0036F4E8
_In_ ULONG InputBufferLength,30
_Out_ PVOID OutputBuffer, 0
_In_ ULONG OutputBufferLength 0
);
__0:000> k__
# ChildEBP RetAddr
00 00cff670 7232df2b ntdll!NtDeviceIoControlFile
01 00cff724 7232dc08 mswsock!SockDoConnectReal+0x2c6
02 00cff7b0 7232e3af mswsock!SockDoConnect+0x412
03 00cff7cc 75ae4d76 mswsock!WSPConnect+0x1f
04 00cff81c 009711df WS2_32!connect+0x86
__WINDBG>dtx _GUID 0036F4E8+10__
(*((_GUID *)0x36f4f8)) : {A42E7CDA-D03F-480C-9CC2-A4DE20ABB878} [Type: _GUID] — HV_GUID_PARENT
[<Raw View>] [Type: _GUID]
__WINDBG>dtx _GUID 0036F4E8+20__
(*((_GUID *)0x36f508)) : {B1D00D3E-FE10-4570-AD62-7648779D7A1B} [Type: _GUID] — GUID, ранее созданный нами в реестре
[<Raw View>] [Type: _GUID]
Переходим в ядро в функцию afd!AfdConnect
. Сперва проверим, был ли вызван сервис из 32- или 64-битного процесса (nt!IoIs32bitProcess
). Затем сравнивается размер куска переданного буфера (начиная с 0x22) с AfdStandardAddressLength
(равной 1Ch). Размер этой структуры равен 24h, поэтому идет выделение пула (ExAllocatePoolWithTagPriority
) в 24h байт и копирование в него переданного буфера. Что интересно, верхняя граница InputBufferLength никак не контролируется, и мы из usermode можем передать любой размер буфера, который, за вычетом 0xC, будет передан функции ExAllocatePoolWithTagPriority
.
Далее вызывается SOCKADDR_SIZE — функция, которая на основании номера протокола получает из массива допустимый размер адреса.
Далее после многочисленных проверок вызывается afd!AfdCreateConnection
(в rcx указатель на элемент структуры afd!AfdTlTransportListHead
). Количество элементов такой структуры можно просмотреть через расширение mex:
__WINDBG>!mex.foreachitem afd!AfdTlTransportListHead -c__
fffff80535407530
ffffc80e74a37bf0
ffffc80e74814b10
ffffc80e742a9400
ffffc80e742a9320
Processed 5 items.
Из нее вызывается nt!PsChargeProcessPoolQuota
, в которой идет работа с переменной PplConnectionPool
. Далее обнуляется область памяти, размером 100h, вызывается ExpInterlockedPopEntrySList
— начинает формироваться новая структура _AFD_CONNECTION, в поле «тип» записывается AFD8.
Далее AfdTimerWheelInitializeEntry
, затем ObfReferenceObject
, где в качестве объекта — процесс ClientExample.exe. AfdReceiveWindowSize
и AfdSendWindowSize
загружаются соответственно в +90h и +94h смещения структуры. Идет проверка esi (туда загружен третий параметр AfdCreateConnection
), если равен нулю, то вызывается nt!IoCreateFile
, однако в нашем случае равен единице. Возвращаемся из AfdCreateConnection
. Вызывается AfdAddConnectedReference
(в качестве первого параметра — указатель на структуру _AFD_CONNECTION), устанавливается в единицу бит 10h этой структуры и увеличивается на единицу бит 30h, затем AfdEnableFailedConnectEvent
(сбрасывается 3ch+8 бит и обнуляется DWORD по смещению 18Ch этой же структуры). Далее выполняются AfdGetEndpointConnectDispatch
(возвращает либо адрес процедуры AfdTlClientConnectDispatch
, либо, если седьмой байт _AFD_CONNECTION равен 10h, — AfdRioTlClientConnectDispatch
) и AfdRefTLBaseEndpoint
.
Затем вызывается hvsocket!VmbusTIProviderConnect
. Внутри вызываются hvsocket!VmbusTlEndpointIsPrivileged
и hvsocket!VmbusTlValidateSockAddress
(в rdx передается структура с двумя GUID’ами, указанными в параметрах сокета от приложения ClientExample, проверяется корректное значение Address Family — 0x22 и переданных GUID’ов, создан ли сервис hvsocket!VmbusTlIsServiceEnabled
).
В hvsocket!VmbusTlIsServiceEnabled
вызывается hvsocket!VmbusTlFindOrCreateService
, находящая GUID сервиса в списке, сформированном на основании значений в реестре:
__WINDBG>!mex.foreachitem @rdx -x "dt nt!_GUID @#Item+10"__
Item #1 @ 0xffffaf89af0af750
nt!_GUID
{999e53d4-3d5c-4c3e-8779-bed06ec056e1}
Item #2 @ 0xffffaf89adac0200
nt!_GUID
{a5201c21-2770-4c11-a68e-f182edb29220}
Item #3 @ 0xffffaf89ade0c870
nt!_GUID
{acef5661-84a1-4e44-856b-6245e69f4620}
Item #4 @ 0xffffaf89adc6a960
nt!_GUID
{b1d00d3e-fe10-4570-ad62-7648779d7a1b}
Item #5 @ 0xffffaf89ad8ddc20
nt!_GUID
{b1d00d3e-fe10-4570-ad62-7648779d7a1c}
Item #6 @ 0xffffaf89ad90bcd8
nt!_GUID
{00000000-0000-0000-0000-000000000000}
Item #7 @ 0xffffaf89adbc1590
nt!_GUID
{7fdfd0ea-cea8-4576-92d6-e072ddd2c422}
Внутри функции идет вызов hvsocket!VmbusTlResolvePartitionId
, параметр передается через xmm0, в который загружается GUID виртуальной машины, полученный через Get-VM -Name $VMName.Id.
В функции hvsocket!VmbusTlFindAndReferencePartition
переданный GUID сравнивается с известными GUID’ами (child partition, zero, parent partition). При совпадении в rdi возвращается тот же GUID.
__WINDBG>dc @rbx__
ffff8907`0a21b5a0 00000004 00000000 00000002 00000000 ................
ffff8907`0a21b5b0 00000001 00000000 00000000 00000000 ................
ffff8907`0a21b5c0 00000000 00000000 00060001 00000000 ................
ffff8907`0a21b5d0 0a21b5d0 ffff8907 0a21b5d0 ffff8907 ..!.......!.....
ffff8907`0a21b5e0 00000000 00000000 00000000 00000000 ................
lea rbx, [rcx-0D8h]
mov rax, [rbx+0E8h]
cmp rax, [rdx]
__WINDBG>dt _GUID @rbx+e8h__
combase!_GUID
{90db8b89-0d35-4f79-8ce9-49ea0ac8b7cd} - HV_GUID_CHILDREN
__WINDBG>dt _GUID @rbx+e8h__
combase!_GUID
{a42e7cda-d03f-480c-9cc2-a4de20abb878} - HV_GUID_PARENT
Функция не находит GUID и возвращает 0, что, в свою очередь, приводит к ошибке 0C0000141.
__WINDBG>!error 0C0000141__
Error code: (NTSTATUS) 0xc0000141 (3221225793) - The address handle given to the transport was invalid.
Затем вызывается hvsocket!VmbusTlCreateConnection
-> hvsocket!VmbusTlCreateEndpoint
-> hvsocket! VmbusTlCreateObjectFromLookasideList
.
До вызова функции
__WINDBG>!mex.foreachitem @r8 -c__
Mex External 3.0.0.7172 Loaded!
ffffdc00c33555a8
Processed 1 items.
WINDBG>!mex.foreachitem ffffdc00c33555a8 -c
ffffdc00c33555a8
ffffc80e75920830
Processed 2 items.
Затем инициализируются Work Queue hvsocket!VmbusTlEndpointActionWorkQueueRoutine
(через netio!NetioInitializeWorkQueue
). Возвращаемся из hvsocket!VmbusTlCreateEndpoint
. Далее инициализируются два DPC: hvsocket!VmbusTlConnectTimeoutDpc
и hvsocket!VmbusTlOppositeEndpointDisconnectTimeoutDpc
(выполняется также инициализация таймера, но в очередь пока не ставится). Возвращаемся из hvsocket!VmbusTlCreateConnection
в hvsocket!VmbusTIProviderConnect
.
Далее идет вызов hvsocket!VmbusTlContainerGetVmId
(подробно рассматривалась ранее в разделе Bind). Затем вызывается hvsocket!VmbusTlAssociateConnectionToPartition
. Параметры:
__WINDBG>r xmm1:ud__
xmm1=78b8ab20 dea4c29c 480cd03f a42e7cda
__WINDBG>r xmm0:ud__
xmm0=cdb7c80a ea49e98c 4f790d35 90db8b89
Затем идет вызов hvsocket!VmbusTlSetupConnection
(второй параметр — тот самый элемент LIST_ENTRY, созданный функцией hvsocket! VmbusTlCreateObjectFromLookasideList
), из которой вызывается hvsocket!VmbusTlSetObjectCancellable
(в ранее созданную AVL-таблицу добавляется один элемент, до вызова функции таблица пустая).
Далее — hvsocket!VmbusTlXPartChildSetupConnection
, откуда вызывается hvsocket!VmbusTlSetupConnectionId
(в нашем случае работа шла с HV_GUID_ZERO, но если переданный GUID отличается, то вызывается nt!ExUuidCreate
и hvsocket!VmbusTlSetEndpointId
), затем вызывается vmbus!ChTlConnectRequest
, и третьим параметром передается GUID созданного нами сервиса:
__WINDBG>dtx _GUID @r8 -r__
(*((_GUID *)0xffffdc00c3355550)) : {B1D00D3E-FE10-4570-AD62-7648779D7A1B} [Type: _GUID]
(если функция вернет ноль, то далее будет возвращена ошибка 0C000009A)
Далее вызывается vmbus!ChAllocateSendMessageSized
-> vmbus!XPartAllocateSendMessage
(сводится к вызову nt!ExAllocatePoolWithTag
и обнулению выделенной памяти) и начинается уже привычная работа с шиной VMBus, описанная в предыдущих статьях (часть 1, часть 2, версия, адаптированная под Win 2016).
Если vmbus!ChAllocateSendMessageSized
выполнилась успешно
__WINDBG>kc__
Call Site
vmbus!ChAllocateSendMessageSized
vmbus!ChTlConnectRequest
hvsocket!VmbusTlXPartChildSetupConnection
hvsocket!VmbusTlSetupConnection
hvsocket!VmbusTlProviderConnect
afd!AfdConnect
и пул был выделен, то выполняется vmbus!ChSendMessage
__WINDBG>dc @rdx — второй параметр vmbus!ChSendMessage__
ffffc80e`758d8ba8 00000015 00000000 00000000 00000000 ................
ffffc80e`758d8bb8 00000000 00000000 b1d00d3e 4570fe10 ........>.....pE
ffffc80e`758d8bc8 487662ad 1b7a9d77
__WINDBG>kc__
Call Site
winhv!WinHvPostMessage
vmbus!PncSendMessage
vmbus!XpartSendMessage (jmp from vmbus!ChSendMessage)
vmbus!ChTlConnectRequest
hvsocket!VmbusTlXPartChildSetupConnection
hvsocket!VmbusTlSetupConnection
hvsocket!VmbusTlProviderConnect
afd!AfdConnect
Идет вызов winhv!WinHvPostMessage
Rcx = 1
Rdx = 1
R9 = 0x28
__WINDBG>dc @r8 @r8+28 — тело сообщения__
ffffa60d`d4eaebb8 00000015 00000000 00000000 00000000 ................
ffffa60d`d4eaebc8 00000000 00000000 b1d00d3e 4570fe10 ........>.....pE
ffffa60d`d4eaebd8 487662ad 1b7a9d77
Перед вызовом winhv!WinHvpHypercallRoutine
(ссылается на vmcall)
Rcx = 0x5c
Rdx = 0
R8 = 23a9000
R9 = 0
__WINDBG>!dd @rdx__
# 62af000 00000001 3ba2286a 00000001 00000028
# 62af010 00000015 00000000 00000000 00000000
# 62af020 00000000 00000000 b1d00d3e 4570fe10
# 62af030 487662ad 1b7a9d77 00000000 00000000
0x15 — CHANNELMSG_TL_CONNECT_REQUEST
0x28 — размер передаваемого сообщения
Сообщение отправлено, возвращаемся в hvsocket!VmbusTlXPartChildSetupConnection
. Вызывается hvsocket!VmbusTlPendConnect
-> hvsocket!VmbusTlPendConnectLocked
. Возвращаемся в hvsocket!VmbusTlSetupConnection
, далее вызывается hvsocket!VmbusTlConnectQueueTimer
(сводится к вызову nt!KeSetTimer, в качестве DPC указывается hvsocket!VmbusTlConnectTimeoutDpc
). Возвращаемся в afd!AfdConnect
, в eax — 103h, поэтому далее вызывается afd!AfdTLPendRequest
и afd!AfdTLConnectComplete2
(afd!AfdCloseConnection
, afd!AfdFinishConnect+nt!IofCompleteRequest
).
Выход из afd.sys
. Гостевая ОС отправила сообщение. Отдельно рассмотрим, каким образом обрабатывается сообщение, отправленное через VMBus. Как известно из предыдущих исследований, все сообщения, отправляемые через гипервызов hvix!HvPostMessage
, проходят обработку в vmbus! ChReceiveChannelMessage
.
Ставим bp на указанную функцию в root ОС, запускаем ServerExample.exe в root ОС, ClientExample.exe в гостевой ОС, останавливаемся. В rdx наше сообщение:
__WINDBG>dc @rdx__
ffffbf01`7b2379f0 00000015 00000000 00000000 00000000 ................
ffffbf01`7b237a00 00000000 00000000 b1d00d3e 4570fe10 ........>.....pE
ffffbf01`7b237a10 487662ad 1b7a9d77
Проходит валидация сообщения, в rax загружается указатель на hvsocket!HvSocketProviderConnectNotification
. Выделяется буфер (Vnpi tag) размером 48h, вызывается netio!NetioInsertWorkQueue
с параметром hvsocket!VmbusTlConnectRequestWorkQueueRoutine
. На этом обработка завершается.
Поставим bp на hvsocket!VmbusTlConnectRequestWorkQueueRoutine
в root OS, перезапустим клиентское приложение. Произойдет остановка. Переходим на hvsocket!VmbusTlProcessConnectRequestWorkItem
, в rcx указатель на три известных нам GUID’а.
__WINDBG>dc @rcx__
ffffae08`c1bbb880 6a964317 4a741d87 a646f9ab 0089049b — VM GUID
ffffae08`c1bbb890 b1d00d3e 4570fe10 487662ad 1b7a9d77 — Service GUID
ffffae08`c1bbb8a0 00000000 00000000 00000000 00000000 — HV_GUID_ZERO
Затем вызывается hvsocket!VmbusTlFindAndReferencePartition
со вторым параметром HV_GUID_CHILDREN, затем hvsocket!VmbusTlFindOrCreateService
, после чего hvsocket!VmbusTlIsServiceEnabled
(возвращает единицу).
Выполняется проверка третьего GUID’а на равенство HV_GUID_ZERO, проходит успешно, вызывается nt!ExUuidCreate
, получаем GUID:
__WINDBG>r xmm0:ud__
xmm0=01cf5129 0c00cd83 11e745c8 da36f003
Затем вызывается hvsocket!VmbusTlProcessNewConnection
. Далее hvsocket!VmbusTlProcessNewConnectionForListener
, из которой вызывается hvsocket!VmbusTlGetPartitionListenerEndpoint
(получаем элемент из AVL-дерева), затем hvsocket!VmbusTlCreateConnection
и hvsocket!VmbusTlAssociateConnectionToPartition
, hvsocket!VmbusTlSetEndpointId
.
Далее — hvsocket!VmbusTlInsertObjectToTable
__WINDBG>dt nt!_RTL_AVL_TABLE ffffae08bfb058a0__
+0x000 BalancedRoot : _RTL_BALANCED_LINKS
+0x020 OrderedPointer : (null)
+0x028 WhichOrderedElement : 0
+0x02c NumberGenericTableElements : 2
+0x030 DepthOfTree : 2
+0x038 RestartKey : (null)
+0x040 DeleteCount : 2
+0x048 CompareRoutine : 0xfffff804`602d16c0 _RTL_GENERIC_COMPARE_RESULTS hvsocket!VmbusTlCompareGuids+0
+0x050 AllocateRoutine : 0xfffff804`602d1db0 void* hvsocket!VmbusTlAllocateForAvlTable+0
+0x058 FreeRoutine : 0xfffff804`602d1dd0 void hvsocket!VmbusTlFreeForAvlTable+0
+0x060 TableContext : 0x00000000`6e6f4350 Void
После этого — hvsocket!VmbusTlSetObjectCancellable
и hvsocket!VmbusTlPendConnect
, hvsocket!VmbusTlListenerProcessPendingIncomingConnection
.
Возвращаемся в NETIO!NetiopIoWorkItemRoutine
. В root ОС при этом
__kd> kcn__
# Call Site
00 winhvr!WinHvPostMessage
01 vmbusr!PncSendMessage
02 vmbusr!XPartSendMessage
03 vmbusr!ChSendOfferMessageLocked
04 vmbusr!ChOfferChannel
05 vmbusr!RootIoctlChannelOffered
06 vmbusr!RootIoctlDispatch
07 vmbusr!RootDeviceControl
……………..WDF stuff
11 vmbusr!RootIoctlDeviceControlPreprocess
…………… WDF stuff
16 vmbkmclr!KmclpSynchronousIoControl
17 vmbkmclr!KmclpServerOfferChannel
18 vmbkmclr!VmbChannelEnable
19 vmbusr!PipeStartChannel
1a vmbusr!PipeOffer
1b hvsocket!VmbusTlXPartRootSetupConnection
1c hvsocket!VmbusTlSetupConnection
1d hvsocket!VmbusTlXPartAcceptConnection
1e hvsocket!VmbusTlListenerProcessPendingIncomingConnection
1f hvsocket!VmbusTlProcessNewConnectionForListener
20 hvsocket!VmbusTlProcessNewConnection
21 hvsocket!VmbusTlProcessConnectRequestWorkItem
22 hvsocket!VmbusTlConnectRequestWorkQueueRoutine
23 NETIO!NetiopIoWorkItemRoutine
24 nt!IopProcessWorkItem
25 nt!ExpWorkerThread
26 nt!PspSystemThreadStartup
27 nt!KiStartSystemThread
__kd> dc @r8 @r8+@r9 — сообщение:__
ffffda85`292c7f30 00000001 00000000 b1d00d3e 4570fe10 ........>.....pE
ffffda85`292c7f40 487662ad 1b7a9d77 0ec85988 11e74d2f .bvHw.z..Y../M..
ffffda85`292c7f50 0c00d483 01cf5129 00000000 00000000 ....)Q..........
ffffda85`292c7f60 00000000 00000000 00002011 00000000 ......... ......
ffffda85`292c7f70 00000000 00000000 00000000 00000000 ................
ffffda85`292c7f80 00000000 00000000 00000000 00000000 ................
ffffda85`292c7f90 00000000 00000000 00000000 00000000 ................
ffffda85`292c7fa0 00000000 00000000 00000000 00000000 ................
ffffda85`292c7fb0 00000000 00000000 00000000 00000000 ................
ffffda85`292c7fc0 00000000 00000000 00000000 00000000 ................
ffffda85`292c7fd0 00000000 00000000 00000000 00000000 ................
ffffda85`292c7fe0 00000000 00000000 0000000b 000100ff ................
ffffda85`292c7ff0 0001000b 004e0079
Также создается порт
__********** Bp winhvr!WinHvCreatePort ********__
rcx=0000000000000001
rdx=0000000080000000
r8=000000000000002d
r9=0000000000000004
# Call Site
00 winhvr!WinHvCreatePort
01 vmbusr!ParentClaimInterruptResources
02 vmbusr!XPartCreateInterrupt
03 vmbusr!ChpInitializeServerChannelLocked
04 vmbusr!ChOfferChannel
05 vmbusr!RootIoctlChannelOffered
06 vmbusr!RootIoctlDispatch
07 vmbusr!RootDeviceControl
…….WDFstuff
11 vmbusr!RootIoctlDeviceControlPreprocess
…….WDFstuff
16 vmbkmclr!KmclpSynchronousIoControl
17 vmbkmclr!KmclpServerOfferChannel
18 vmbkmclr!VmbChannelEnable
19 vmbusr!PipeStartChannel
1a vmbusr!PipeOffer
1b hvsocket!VmbusTlXPartRootSetupConnection
1c hvsocket!VmbusTlSetupConnection
1d hvsocket!VmbusTlXPartAcceptConnection
1e hvsocket!VmbusTlListenerProcessPendingIncomingConnection
1f hvsocket!VmbusTlProcessNewConnectionForListener
20 hvsocket!VmbusTlProcessNewConnection
21 hvsocket!VmbusTlProcessConnectRequestWorkItem
22 hvsocket!VmbusTlConnectRequestWorkQueueRoutine
23 NETIO!NetiopIoWorkItemRoutine
24 nt!IopProcessWorkItem
25 nt!ExpWorkerThread
26 nt!PspSystemThreadStartup
27 nt!KiStartSystemThread
и выполняется подключение к нему:
********** Bp winhvr!WinHvConnectPort ********
rcx=0000000000000004
rdx=0000000080000000
r8=000000000001000c
r9=0000000000000001
# Call Site
00 winhvr!WinHvConnectPort
01 vmbusr!ParentConnectDedicatedInterrupt
02 vmbusr!ParentClaimInterruptResources
03 vmbusr!XPartCreateInterrupt
04 vmbusr!ChpInitializeServerChannelLocked
05 vmbusr!ChOfferChannel
06 vmbusr!RootIoctlChannelOffered
07 vmbusr!RootIoctlDispatch
08 vmbusr!RootDeviceControl
………….WDFStuff
12 vmbusr!RootIoctlDeviceControlPreprocess
………….WDFStuff
17 vmbkmclr!KmclpSynchronousIoControl
18 vmbkmclr!KmclpServerOfferChannel
19 vmbkmclr!VmbChannelEnable
1a vmbusr!PipeStartChannel
1b vmbusr!PipeOffer
1c hvsocket!VmbusTlXPartRootSetupConnection
1d hvsocket!VmbusTlSetupConnection
1e hvsocket!VmbusTlXPartAcceptConnection
1f hvsocket!VmbusTlListenerProcessPendingIncomingConnection
20 hvsocket!VmbusTlProcessNewConnectionForListener
21 hvsocket!VmbusTlProcessNewConnection
22 hvsocket!VmbusTlProcessConnectRequestWorkItem
23 hvsocket!VmbusTlConnectRequestWorkQueueRoutine
24 NETIO!NetiopIoWorkItemRoutine
25 nt!IopProcessWorkItem
26 nt!ExpWorkerThread
27 nt!PspSystemThreadStartup
28 nt!KiStartSystemThread
Затем в гостевой ОС выполняется nt!IoRegisterDeviceInterface
:
# Call Site
00 nt!IoRegisterDeviceInterface
01 Wdf01000!Mx::MxRegisterDeviceInterface
02 Wdf01000!FxDeviceInterface::Register
03 Wdf01000!FxDeviceInterface::Register
04 Wdf01000!imp_WdfDeviceCreateDeviceInterface
05 vmbus!RootStartDeviceInterfaceByContext
06 hvsocket!VmbusTlXPartProcessNewConnection
07 vmbus!RootNotifyDeviceInterfaceArrival
08 Wdf01000!FxWorkItem::WorkItemHandler
09 Wdf01000!FxWorkItem::WorkItemThunk
0a nt!IopProcessWorkItem
0b nt!ExpWorkerThread
0c nt!PspSystemThreadStartup
0d nt!KiStartSystemThread
NTSTATUS IoRegisterDeviceInterface(
_In_ PDEVICE_OBJECT PhysicalDeviceObject,
_In_ const GUID *InterfaceClassGuid,
_In_opt_ PUNICODE_STRING ReferenceString,
_Out_ PUNICODE_STRING SymbolicLinkName
);
kd> !devobj @rcx
Device object (ffffe38bf77145b0) is for:
00000013 \Driver\ACPI DriverObject ffffe38bf79d5a00
Current Irp 00000000 RefCount 0 Type 00000032 Flags 00001040
SecurityDescriptor ffffa48e93c56dc0 DevExt ffffe38bf7a71c60 DevObjExt ffffe38bf7714700 DevNode ffffe38bf79d1c50
ExtensionFlags (0000000000)
Characteristics (0x00000180) FILE_AUTOGENERATED_DEVICE_NAME, FILE_DEVICE_SECURE_OPEN
AttachedDevice (Upper) ffffe38bf7af5970 \Driver\vmbus
Device queue is not busy.
__kd> dx _GUID @rdx__
(*((_GUID *)0xffffa48ea28888f0)) : {B1D00D3E-FE10-4570-AD62-7648779D7A1B} [Type: _GUID]
__kd> dx UNICODE_STRING @r8__
(*((UNICODE_STRING *)0xffffa48ea2888900)) : "{b1d00d3e-fe10-4570-ad62-7648779d7a1b}-{00000000-0000-0000-0000-000000000000}-0000" [Type: UNICODE_STRING]
Затем выполняется функция nt!PnpNotifyDeviceClassChange
__kd> k__
# Child-SP RetAddr Call Site
00 nt!PnpNotifyDeviceClassChange
01 nt!PnpDeviceEventWorker+0x263
02 nt!ExpWorkerThread+0xe9
03 nt!PspSystemThreadStartup+0x41
04 nt!KiStartSystemThread+0x16
__kd> dc @rcx__
ffffa48e`9f13f3f8 cb3a4004 11d046f0 60008fb0 3f051397 .@:..F.....`...?
__kd> dx _GUID @rdx__
(*((_GUID *)0xffffa48ea0dcd0a8)) : {B1D00D3E-FE10-4570-AD62-7648779D7A1B} [Type: _GUID]
__kd> du @rdx+10__
ffffa48e`a0dcd0b8 "\??\ACPI#VMBus#0#{b1d00d3e-fe10-"
ffffa48e`a0dcd0f8 "4570-ad62-7648779d7a1b}\{b1d00d3"
ffffa48e`a0dcd138 "e-fe10-4570-ad62-7648779d7a1b}-{"
ffffa48e`a0dcd178 "00000000-0000-0000-0000-00000000"
ffffa48e`a0dcd1b8 "0000}-0000"
Стек потока ClientExample.exe при этом:
Child-SP RetAddr Call Site
ffffd180`715e1070 fffff800`081690d5 nt!KxDispatchInterrupt+0x122
ffffd180`715e11b0 fffff800`08169d32 nt!KiDpcInterruptBypass+0x25
ffffd180`715e11c0 fffff800`07e6a003 nt!KiVmbusInterrupt2+0x212 (TrapFrame @ ffffd180`715e11c0)
ffffd180`715e1358 fffff809`c86a17ff 0xfffff800`07e6a003
ffffd180`715e1360 fffff809`c86a19b4 winhv!WinHvpHypercall+0x57
ffffd180`715e13a0 fffff809`c86a1f96 winhv!WinHvpSimplePoolHypercall+0x40
ffffd180`715e13e0 fffff809`c8628f92 winhv!WinHvPostMessage+0x8e
ffffd180`715e1470 fffff809`c8628664 vmbus!PncSendMessage+0x42
ffffd180`715e14a0 fffff809`c863c08d vmbus!XPartSendMessage+0x60
ffffd180`715e14f0 fffff809`c8669b24 vmbus!ChTlConnectRequest+0x4d
ffffd180`715e1530 fffff809`c8665e67 hvsocket!VmbusTlXPartChildSetupConnection+0xb4
ffffd180`715e1580 fffff809`c8662fe5 hvsocket!VmbusTlSetupConnection+0x18b
ffffd180`715e15d0 fffff809`c98970e2 hvsocket!VmbusTlProviderConnect+0x615
ffffd180`715e1680 fffff800`0842e180 afd!AfdConnect+0x6b2
ffffd180`715e1820 fffff800`0842d064 nt!IopSynchronousServiceTail+0x1a0
ffffd180`715e18e0 fffff800`0842c9e6 nt!IopXxxControlFile+0x674
ffffd180`715e1a20 fffff800`08170493 nt!NtDeviceIoControlFile+0x56
ffffd180`715e1a90 00000000`61e7222c nt!KiSystemServiceCopyEnd+0x13 (TrapFrame @ ffffd180`715e1b00)
Send
Наиболее функциональная часть — прием и передача данных. Рассмотрим send. Recv, по идее, принципиально отличаться не должен. Рассматривать будем клиентское приложение. В usermode вызывается mswsock!WPSend
, привычный NtDeviceIoControlFile
NTSTATUS WINAPI NtDeviceIoControlFile(
_In_ HANDLE FileHandle, - 144 (\Device\Afd)
_In_ HANDLE Event, - 140 (Event)
_In_ PIO_APC_ROUTINE ApcRoutine, 0
_In_ PVOID ApcContext, 0
_Out_ PIO_STATUS_BLOCK IoStatusBlock, 00DBF45C
_In_ ULONG IoControlCode, 1201F
_In_ PVOID InputBuffer, DBF44C
_In_ ULONG InputBufferLength,10
_Out_ PVOID OutputBuffer, 0
_In_ ULONG OutputBufferLength 0
);
Однако в стандартный обработчик afd!AfdDispatchDeviceControl
мы не попадем, вместо этого обработку кода будет выполнять afd!AfdFastIoDeviceControl
. При инициализации драйвера регистрируется соответствующий обработчик:
objDrv->FastIoDispatch = &AfdFastIoDispatch;
__WINDBG>kc__
Call Site
winhv!WinHvSignalEvent
vmbus!BusChSendInterrupt
vmbkmcl!KmclSendSignal
vmbus!PipeWrite
hvsocket!VmbusTlXPartProcessIoRequest
hvsocket!VmbusTlConnectProcessIoRequest
hvsocket!VmbusTlConnectionSend
afd!AfdFastConnectionSend
afd!AfdFastIoDeviceControl
nt!IopXxxControlFile
nt!NtDeviceIoControlFile
nt!KiSystemServiceCopyEnd
wow64cpu!CpupSyscallStub
wow64cpu!DeviceIoctlFileFaul
При вызове winhv!WinHvpHypercallRoutine
параметры следующие:
__WINDBG>r__
rcx=000000000001005d — hypercall code
rdx=000000000001000a — CONNECTION_ID
r8 = 0
В исследовании процесса передачи данных компонентом Hyper-V Data Exchange (Hyper-V Internals, раздел Integration Services — Data Exchange) в случае передачи через общий буфер сигналом для его считывания станет вызов WinHvSignalEvent
. Общий буфер был выделен ранее, он представляет собой область памяти, доступной для чтения/записи как гостевой ОС, так и root OS. Чтобы добраться до нее, необходимо поставить точку останова на vmbusr!PkGetReceiveBuffer
и просмотреть буфер, указатель на который расположен в rcx+18h. Размер буфера достаточно большой — в 2012 r2 для него выделялось десять физических страниц, 40 Кбайт.
__WINDBG>dc ffffbf01`7c9a9000 L1000__
ffffbf01`7c9a9000 00000028 00000000 00000001 00000000 (...............
ffffbf01`7c9a9010 00000000 00000000 00000000 00000000 ................
……………………………………………………………………………………………………………….
ffffbf01`7c9aa000 00020006 00000004 00000000 00000000 ................
ffffbf01`7c9aa010 00000001 00000008 74736554 74736554 ........TestTest
ffffbf01`7c9aa020 00000000 00000000 00000000 00000000 ................
__WINDBG>kcn__
\ # Call Site
00 vmbusr!PkGetReceiveBuffer
01 vmbusr!PipeValidateAndGetReceiveBuffer
02 vmbusr!PipeForwardToValidPacket
03 vmbusr!PipeTryReadOrPeekSingle
04 vmbusr!PipePeekMultiple
05 vmbusr!PipeProcessDeferredReadWrite
06 vmbusr!PipeProcessDeferredIosAndUnlock
07 vmbusr!PipeEvtChannelSignalArrived
08 vmbkmclr!KmclpVmbusManualIsr
09 vmbusr!ParentRingInterruptDpc
0a nt!KiExecuteAllDpcs
0b nt!KiRetireDpcList
0c nt!KiIdleLoop
В той же процедуре в очередь добавляется Work Item.
kd> k
# Child-SP RetAddr Call Site
00 NETIO!NetioInsertWorkQueue
01 hvsocket!VmbusTlQueueEndpointAction+0x14e
02 hvsocket!VmbusTlDeliverDataIndications+0x6b
03 hvsocket!VmbusTlXPartIndicateReceive+0x9b
04 vmbusr!PipePeekMultiple+0xf3
05 vmbusr!PipeProcessDeferredReadWrite+0x1b6
06 vmbusr!PipeProcessDeferredIosAndUnlock+0x74
07 vmbusr!PipeEvtChannelSignalArrived+0x91
08 vmbkmclr!KmclpVmbusManualIsr+0x1d
09 vmbusr!ParentRingInterruptDpc+0x62
0a nt!KiExecuteAllDpcs+0x2b1
0b nt!KiRetireDpcList+0x5df
0c nt!KiIdleLoop+0x5a
Далее он выполняется, данные копируются в буфер драйвера hvsocket.sys
, затем передаются приложению через тот же Fast I/O.
__WINDBG>kcn__
# Call Site
00 hvsocket!VmbusTlIndicateReceive
01 hvsocket!VmbusTlConnectIoRequestCompleted
02 hvsocket!VmbusTlXPartIoRequestCompleted
03 nt!IopfCompleteRequest
04 hvsocket!VmbusTlFulfillReceiveRequest
05 hvsocket!VmbusTlDeliverSingleDataIndicationList
06 hvsocket!VmbusTlDeliverDataIndications
07 hvsocket!VmbusTlEndpointActionWorkQueueRoutine
08 NETIO!NetiopIoWorkItemRoutine
09 nt!IopProcessWorkItem
0a nt!ExpWorkerThread
0b nt!PspSystemThreadStartup
0c nt!KiStartSystemThread
PowerShell Direct
Windows PowerShell достаточно давно поддерживает протокол PowerShell Remoting, который позволяет подключаться к рабочим станциям и серверам по сети для удаленного управления и выполнения произвольных операций. PowerShell Remoting описан Microsoft в документе [MS-PSRP], который выложен в открытый доступ в рамках программы Open Specifications. PowerShell Direct для своей работы использует тот же протокол, однако средой доставки данных служит не сеть на базе TCP/IP-стека, а шина VMBus.
Для работы PowerShell Direct используются те же самые командлеты, что и для PowerShell Remoting: Enter-PSSession, Invoke-PSSession и New-PSSession, только вместо имени компьютера указывается имя виртуальной машины или ее GUID.
Для поддержки этой технологии в гостевой ОС была создана отдельная служба Hyper-V PowerShell Direct Service (имя службы — vmicvmsession), функциональность которой реализована в библиотеке %SystemRoot%\System32\ICSvc.dll. Тип запуска — Manual (Trigger start). При каких условиях запустится служба?
__PS C:\Users\Administrator> sc.exe qtriggerinfo vmicvmsession__
[SC] QueryServiceConfig2 SUCCESS
SERVICE_NAME: vmicvmsession
START SERVICE
DEVICE INTERFACE ARRIVAL : 999e53d4-3d5c-4c3e-8779-bed06ec056e1 [INTERFACE CLASS GUID] - HV_GUID_VM_SESSION_SERVICE_ID
Это произойдет, если к системе будет подключено устройство с INTERFACE CLASS GUID 999e53d4-3d5c-4c3e-8779-bed06ec056e1
. Как видно, GUID этого устройства совпадает с GUID’ом сервиса из раздела реестра в root-разделе (HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\GuestCommunicationServices
), где для работы с сокетами по умолчанию созданы два ключа с GUID’ом:
999e53d4-3d5c-4c3e-8779-bed06ec056e1
;a5201c21-2770-4c11-a68e-f182edb29220
.
В гостевой ОС устройства с таким GUID’ом присутствуют в
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses\{999e53d4-3d5c-4c3e-8779-bed06ec056e1}
и
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses\{a5201c21-2770-4c11-a68e-f182edb29220}\##?#ACPI#VMBus#0#{a5201c21-2770-4c11-a68e-f182edb29220}\#{a5201c21-2770-4c11-a68e-f182edb29220}-{00000000-0000-0000-0000-000000000000}-0000
При выполнении командлетов в root ОС инициализируется сокет, при этом в гостевой ОС сервисом vmicvmsession создается процесс Powershell.exe и один сокет Hyper-V. Данные, преобразованные в XML-формат, передаются стандартными вызовами send в зашифрованном Base64-виде. Механизм не требует включения в опциях виртуальной машины и доступен по умолчанию. Ранее для того, чтобы узнать алгоритм работы, исследователи декомпилировали модуль System.Management.Automation.dll, входящий в состав Windows PowerShell, теперь же ситуация упростилась и готовый код можно найти на GitHub: исходные коды PowerShell.
Здесь можно почерпнуть много информации, в частности о том, каким же образом реализовать свое собственное приложение для работы с сокетами Hyper-V на C#. Подобные примеры на C++ уже можно было найти в Сети.
Поставим символьные точки останова bm nt!PnpNotif* в гостевой ОС, и при выполнении Enter-PSSession мы остановимся на nt!PnpNotifyDeviceClassChange
.
__kd> kcn__
# Call Site
00 nt!PnpNotifyDeviceClassChange
01 nt!PnpDeviceEventWorker
02 nt!ExpWorkerThread
03 nt!PspSystemThreadStartup
При этом в rcx находится указатель на некий GUID. Вторым параметром передается GUID сервиса VM Session Service 1.
__kd> dc @rcx__
ffffa806`a2b5d6c8 cb3a4004 11d046f0 60008fb0 3f051397
ffffa806`a2b5d6d8 00000002 00000000 00000000 00000000
ffffa806`a2b5d6e8 00000000 00000164 00000000 00000000
__kd> dc @rdx__
ffffa806`a2b5d6f8 999e53d4 4c3e3d5c d0be7987 e156c06e
__kd> du @rdx+10 — по смещению 10h — ID устройства, которое появляется на шине VMBus__
ffffa806`a2b5d708 "\??\ACPI#VMBus#0#{999e53d4-3d5c-"
ffffa806`a2b5d748 "4c3e-8779-bed06ec056e1}\{999e53d"
ffffa806`a2b5d788 "4-3d5c-4c3e-8779-bed06ec056e1}-{"
ffffa806`a2b5d7c8 "00000000-0000-0000-0000-00000000"
ffffa806`a2b5d808 "0000}-0000"
Тем не менее это устройство не отображается в списке дочерних устройств VMBus (!devnode 0 1). В разделе HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\VMBus
эти GUID’ы также отсутствуют. Однако параметр DeviceInstance в разделе HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceClasses\{999e53d4-3d5c-4c3e-8779-bed06ec056e1}\##?#ACPI#VMBus#0#{999e53d4-3d5c-4c3e-8779-bed06ec056e1}
имеет значение ACPI\VMBUS\0
, что полностью совпадает со значением Device Instance устройства VMBus.
Также при выполнении Enter-PSSession срабатывает функция vmbus!RootAddDeviceInterface
и nt!IoRegisterDeviceInterface
:
__kd> kcn__
# Call Site
00 nt!IoRegisterDeviceInterface
01 Wdf01000!Mx::MxRegisterDeviceInterface
02 Wdf01000!FxDeviceInterface::Register
03 Wdf01000!FxDeviceInterface::Register
04 Wdf01000!imp_WdfDeviceCreateDeviceInterface
05 vmbus!RootStartDeviceInterfaceByContext
06 hvsocket!VmbusTlXPartProcessNewConnection
07 vmbus!RootNotifyDeviceInterfaceArrival
08 Wdf01000!FxWorkItem::WorkItemHandler
09 Wdf01000!FxWorkItem::WorkItemThunk
0a nt!IopProcessWorkItem
0b nt!ExpWorkerThread
0c nt!PspSystemThreadStartup
0d nt!KiStartSystemThread
Последняя функция документирована в MSDN.
NTSTATUS IoRegisterDeviceInterface(
_In_ PDEVICE_OBJECT PhysicalDeviceObject,
_In_ const GUID *InterfaceClassGuid,
_In_opt_ PUNICODE_STRING ReferenceString,
_Out_ PUNICODE_STRING SymbolicLinkName
);
__kd> !devobj @rcx — PhysicalDeviceObject__
Device object (ffffc9094ca24630) is for:
00000013 \Driver\ACPI DriverObject ffffc9094cdeea00
Current Irp 00000000 RefCount 0 Type 00000032 Flags 00001040
SecurityDescriptor ffffb90d7d06a630 DevExt ffffc9094ce89c60 DevObjExt ffffc9094ca24780 DevNode ffffc9094cda7c50
ExtensionFlags (0000000000)
Characteristics (0x00000180) FILE_AUTOGENERATED_DEVICE_NAME, FILE_DEVICE_SECURE_OPEN
AttachedDevice (Upper) ffffc9094ceb4970 \Driver\vmbus
Device queue is not busy
__kd> dx _GUID @rdx — InterfaceClassGuid__
(*((_GUID *)0xffffb90d7ed57eb0)) : {999E53D4-3D5C-4C3E-8779-BED06EC056E1} [Type: _GUID]
[<Raw View>] [Type: _GUID]
__kd> dx _UNICODE_STRING @r8 — ReferenceString__
(*((_UNICODE_STRING *)0xffffb90d7ed57ec0)) : "{999e53d4-3d5c-4c3e-8779-bed06ec056e1}-{00000000-0000-0000-0000-000000000000}-0000" [Type: _UNICODE_STRING]
При выполнении Enter-PSSession
в root ОС идет вызов ws2_32!connect
, в ходе которого в гостевую ОС посредством вызова winhvr!WinHvPostMessage
передается сообщение
__kd> !dc @rdx — непосредственно перед вызовом vmcall (часть тела сообщения)__
#227b36000 00000001 00000000 00000001 000000c4 ................
#227b36010 00000001 00000000 999e53d4 4c3e3d5c .........S..\=>L
#227b36020 d0be7987 e156c06e
Далее в гостевой ОС, как мы видели, выполняется nt!IoRegisterDeviceInterface
и nt!PnpNotifyDeviceClassChange
, после чего срабатывает триггер на запуск службы vmicvmsession.
Но мы можем заменить передаваемый GUID на любой другой, что спровоцирует запуск других служб, у которых есть аналогичный триггер. В настоящее время это следующие службы:
- All Hyper-V guest services;
- Bluetooth Support Service;
- Windows Camera Frame Server;
- Human Interface Device Service;
- Geolocation Service;
- Microsoft Passport;
- Portable Device Enumerator Service;
- Sensor Service;
- Sensor Monitoring Service;
- Storage Service;
- Touch Keyboard and Handwriting (probably).
С технической точки зрения мы просто эмулируем подключение определенного устройства к системе. Реальные устройства, выполняющие эмуляцию, не появляются, и некоторые службы в таких условиях просто не работают и после запуска выдают сообщение.
На Shielded VM (проверено в режиме Admin-Trusted) эффект аналогичен, однако служба Hyper-V PowerShell Direct Service запускаться не будет.
Что интересно, значение параметра Credentials для Enter-PSSession, который используется для запуска powershell.exe в гостевой ОС, передается в открытом виде.
if (emptyPassword)
{
HyperVSocket.Send(Encoding.ASCII.GetBytes("EMPTYPW"));
HyperVSocket.Receive(response);
responseString = Encoding.ASCII.GetString(response);
}
else
{
HyperVSocket.Send(Encoding.ASCII.GetBytes("NONEMPTYPW"));
HyperVSocket.Receive(response);
HyperVSocket.Send(password);
HyperVSocket.Receive(response);
responseString = Encoding.ASCII.GetString(response);
.............................................................
В родительском разделе — bp ws2_32!recv
в процессе PowerShell.exe.
В дочернем разделе — bp sspicli!LogonUserExExW
.
Для просмотра XML-сообщений ставились точки останова на функции recv и send в гостевой и root ОС. В RAW-формате они выглядят следующим образом.
Если подгрузить расширение для отладки .NET (.cordll -ve -u -l), то можно увидеть стек процесса powershell.exe.
Как видим, отправка производится функцией SendOneItem из модуля OutOfProcessTransportManager.cs.
Команда, переданная на исполнение в гостевую ОС, кодируется функцией CreateDataPacket все из того же модуля OutOfProcessTransportManager.cs.
internal static string CreateDataPacket(byte[] data, DataPriorityType streamType, Guid psGuid)
{
string result = string.Format(CultureInfo.InvariantCulture,
"<{0} {1}='{2}' {3}='{4}'>{5}</{0}>",
PS_OUT_OF_PROC_DATA_TAG, - Data
PS_OUT_OF_PROC_STREAM_ATTRIBUTE, - Stream
streamType.ToString(), - Default
PS_OUT_OF_PROC_PSGUID_ATTRIBUTE, - PSGUID
psGuid.ToString(), - 223adb3d-b639-4e84-aa83-6b193db87e1e
Convert.ToBase64String(data)); - BASE64 текст
return result;
}
Возможные заголовки пакетов.
Например, если мы откроем сессию PowerShell Direct и выполним команду mkdir С:\Tools\Test
в гостевой ОС, то текстовая часть диалога в целом будет такой.
Диалог выглядит примерно следующим образом: root ОС отправляет гостевой ОС несколько XML-сообщений, в одном из которых содержится команда
и аргументы команды.
Гостевая ОС возвращает несколько сообщений, которые, помимо служебной информации, содержат и результаты выполнения команды (каждая строка вывода команды mkdir передается как отдельный объект).
В принципе, можно получить незакодированные данные, поставив точку останова на System.Management.Automation.Remoting.Client.OutOfProcessClientCommandTransportManager.SendData (адрес можно увидеть в выводе команды !clrstack). Остановившись на нужной точке, выполняем команду !clrstack -a
, получаем адрес переменной data:
__0:020> !clrstack -a__
OS Thread Id: 0x3b8 (20)
………………………
PARAMETERS:
this (<CLR reg>) = 0x000001e0a8459060
data (<CLR reg>) = 0x000001e0a84964b0
priorityType (<CLR reg>) = 0x0000000000000000
Размер буфера можно узнать:
0:020> !DumpObj /d 000001e0a84964b0
Name: System.Byte[]
MethodTable: 00007ffc081993d0
EEClass: 00007ffc07bd4dc8
Size: 2219(0x8ab) bytes
Выполнив команду dc
, можно увидеть содержимое пакета.
На странице описания PowerShell Direct указано, что для работы механизма сессий нужны привилегии Hyper-V Administrators.
Фактически они нужны для запуска командлета Enter-PSSession
, сами же сокеты Hyper-V не требуют каких-либо привилегий для работы, так что их функциональностью можно пользоваться в приложениях, запущенных от обычного пользователя.
Из root ОС мы можем подключиться к сервису vmicvmsession. Для этого в приложении ClientExample.exe заменим HV_PARENT_GUID на GUID виртуальной машины.
WSADATA wsaData;
SOCKADDR_HV clientService;
CLSID VmID, ServiceID;
// Initialize GUIDs
//wchar_t* clsid_str = L"{a42e7cda-d03f-480c-9cc2-a4de20abb878}"; // HV_PARENT_GUID
wchar_t* clsid_str = L"{6a964317-1d87-4a74-abf9-46a69b048900}";
CLSIDFromString(clsid_str, &VmID);
clsid_str = L"{999e53d4-3d5c-4c3e-8779-bed06ec056e1}";
CLSIDFromString(clsid_str, &ServiceID); // GUID of Powershell Direct Service
Скомпилируем, запустим и получим ответ от гостевой ОС.
Так начинает взаимодействие с клиентом модуль icsvc.dll. Протокол взаимодействия клиентской части присутствует в исходных кодах PowerShell в файлах OutOfProcTransportManager.cs и RemoteSessionHyperVSocket.cs, поэтому технически нет препятствий к тому, чтобы скопировать этот код в отдельное приложение и использовать его как полноценный клиент для коммуникаций со службой PowerShell Direct без каких-либо дополнительных привилегий. Разумеется, останется необходимость указывать учетные данные, под которыми будет запущен процесс powershell.exe в гостевой ОС.
Таблица портов, создаваемых root ОС для коммуникаций с гостевой ОС. При использовании PowerShell Direct создаются два дополнительных порта.
После этого в целом картина становится более понятной: при выполнении командлета Enter-PSSession в гостевой ОС инициируется регистрация нового интерфейса для драйвера шины VMBus, которая приводит к старту службы vmicsession, а она, в свою очередь, запускает дочерний PowerShell-процесс под учетными данными, переданными из root ОС. Этот процесс открывает сокеты Hyper-V и начинает процесс коммуникаций с процессом powershell.exe, работающим в root ОС, с помощью обычных вызовов send и recv.
Также стоит отметить, что некоторую логику выполнения драйверов (в частности, hvsocket.sys) можно узнать, используя Windows software trace preprocessor (WPP).
Во многих драйверах Microsoft используется WPP, записи которого позволяет посмотреть утилита traceview из WDK. Для этого необходимо узнать GUID трассировки. Если посмотреть в IDA, то наименование переменной, в которой содержится GUID, будет выглядеть как WPP_ThisDir_CTLGUID_HvSocketTraceGuid или WPP_ThisDir_CTLGUID_VMBusDriverTraceGuid. GUID будет в бинарном виде: 0B8A5B44354C0BBA849083340689010E5h
. Его нужно преобразовать в обычный формат (689010e5-3340-4908-a8bb-c05443b4a5b8)
, например набрав команду dt _GUID <адрес переменной>
в WinDBG.
Полученный GUID можно вбить в TraceView, начав новую сессию: File -> Create Ne Log Session -> Add Provider.
Затем указать путь к файлу, в который будут записываться данные. Картина получится не слишком информативная (TMF-файл для декодирования Microsoft не предлагает).
Но если мы загрузим сохраненный etl-файл в Windows Message Analyzer, то мы сможем увидеть PID и TID, а также само WPP-сообщение в RAW-виде.
Можно просмотреть в WinDBG, что же содержится по этим адресам. Подобные значения встречались на этапе инициализации сокета:
__kd> dc FFFF9D0C351266C0__
ffff9d0c`351266c0 00000001 00000000 00000004 00000000 ................
ffff9d0c`351266d0 00000001 00000000 00000000 00000000 ................
ffff9d0c`351266e0 00000000 00000000 00060001 00000000 ................
__kd> dc FFFF9D0C34A9C340__
ffff9d0c`34a9c340 00000001 00000000 00000003 00000000 ................
ffff9d0c`34a9c350 00000001 00000000 00000000 00000000 ................
ffff9d0c`34a9c360 00000000 00000000 00060001 00000000 ................
В целом можно сделать вывод, что PowerShell Direct создавался для случаев, когда администратор Hyper-V и администратор виртуального сервера — одно и то же лицо. Слишком большой и разноплановый поток данных идет из гостевой в хост-ОС и обрабатывается процессом powershell.exe, запущенным с привилегиями Hyper-V Administrator (таково требование для работы PowerShell Direct). Конечно, вряд ли, отправив сообщения с тегом
Заключение
В статье мы рассмотрели некоторые аспекты работы сокетов Hyper-V. По результатам можно понять, что с архитектурной точки зрения их работа мало чем отличается от работы обычных сокетов, однако для их поддержки были внесены определенные изменения в компоненты сетевого стека.
На наш взгляд, это первый документированный канал для двухстороннего обмена данными между гостевой и родительской операционными системами в среде виртуализации Hyper-V. Его можно использовать, например, для обмена данными между USB-устройством, подключенным к хост-ОС, и дочерней ОС, что полезно для аппаратных ключей защиты или двухфакторной аутентификации при входе в операционную систему.
Разумеется, приведенный выше пример используется исключительно для демонстрации их работы. Как, например, будет работать этот механизм передачи данных для многопоточных приложений или при передаче большого объема данных, сейчас сказать сложно. Посмотрим, будут ли использовать сторонние разработчики предоставленную компанией Microsoft возможность передачи данных.