Хакер — это в первую очередь программист-исследователь, человек, который глубоко интересуется информационными технологиями, старается проникнуть в суть вещей, найти самые неочевидные и недокументированные возможности. Нам, команде «Хакера», не дают покоя лавры Марка Руссиновича (мы про книгу «Внутреннее устройство Microsoft Windows» :)), поэтому посвятим эту огромную статью оригинальному исследованию внутренних механизмов сокетов Hyper-V.

Технология виртуализации 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) и намекает на то, что разработчики смогут создавать свои решения на базе новой технологии виртуализации.







WWW

Предыдущее исследование Hyper-V под названием Исследуем внутренние механизмы работы Hyper-V (в двух частях) читай тут:

Действительно, в этом направлении были сделаны определенные шаги, и у разработчиков появились:

  • заголовочные файлы 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). Конечно, вряд ли, отправив сообщения с тегом в хост-ОС, получится выполнить произвольную команду, но то, что в зависимых компонентах Windows (.NET, XML) уязвимости находят чаще, чем в модуле гипервизора, наглядно видно, например, в базе данных уязвимостей NVD. Впрочем, специфические условия использования уязвимостей могут сделать их бесперспективными (нужен терминальный сервер XenApp\RDS, где пользователь смог получить привилегии до администратора и где администратор Hyper-V выполняет команды с помощью PowerShell Direct).

 

Заключение

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

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

Разумеется, приведенный выше пример используется исключительно для демонстрации их работы. Как, например, будет работать этот механизм передачи данных для многопоточных приложений или при передаче большого объема данных, сейчас сказать сложно. Посмотрим, будут ли использовать сторонние разработчики предоставленную компанией Microsoft возможность передачи данных.

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    3 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии