Hooking into NDIS and TDI

Дата публикации 26 авг 2017 | Редактировалось 18 сен 2017

Перевод статьи «Hooking into NDIS and TDI, part 1»


Перехват NDIS и TDI, часть 1



andreas пишет:
Это первая часть из 2-х статей, описывающих перехват на уровне NDIS и TDI. В первой части мы обсудим перехват на уровне NDIS, а во второй на уровне TDI.

Прежде всего давайте посмотрим на упрощенное изображение сетевого стека в пространстве ядра:

TDI
NDIS уровень протокола
NDIS промежуточный уровень
Miniport
Hardware​

Для контроля потока данных на уровне NDIS у нас есть 3 потенциально возможные точки для добавления устройства/драйвера либо перехвата уже существующих. Прежде всего у нас есть уровень мини-порта – драйверы, контролирующие NIC оборудование, но это немного «низковато» для того, что нам надо. Далее у нас есть промежуточный уровень. Этот уровень является наиболее подходящим для наших целей, поскольку он позволит нам контролировать поток данных ко всем NDIS драйверам протоколов. Но тут есть главная помеха: драйвер должен быть с цифровой подписью, чтобы он мог быть добавленным на этот слой.

В зависимости от системы и при каких обстоятельствах мы устанавливаем этот код, нам возможно не удастся легко справится с этой проблемой. Последним у нас идет NDIS протокольного уровня. Добавление драйвера на этот уровень не составит проблем, как к примеру, это делает программа WinPCap. Однако в этом случае мы не сможем контролировать, то что будет видеть пользователь при помощи программ, которые базируются на таких типах драйверов (на пример Ethereal). И все же существует ли способ обхода вопроса цифровой подписи драйвера и в то же время контроля данных на протокольном слое и выше? Да! Мы можем виртуально добавить слой между промежуточным и протокольным уровнем, перехватывая все NDIS протокольные драйвера и их протокольные функции.

После регистрации NDIS протокола он добавляется в связный список. Каждый элемент в этом списке содержит указатель на структуру под названием NDIS_OPEN_BLOCK. Эта структура содержит указатели на все зарегистрированные функциональные указатели протокола. Элементы связного списка представляют собой структуру похожую на следующую:
Код (C++):
  1. typedef struct _NDIS_LINKED_LIST {
  2.   PNDIS_OPEN_BLOCK pOpenBlock;
  3.   PVOID p;
  4.   REFERENCE ref;
  5.   struct _NDIS_LINKED_LIST *Next;
  6. } NDIS_LINKED_LIST,*PNDIS_LINKED_LIST;
Прошло что-то около года с тех пор, как я игрался с этим кодом, поэтому я в принципе не помню название этой структуры. Интересующиеся могут найти его при помощи гугла. Это также отразится позже в исходном коде, который базируется на абсолютных смещениях вместо определенной выше структуры.

Для того, чтобы иметь возможность перехватить все зарегистрированные NDIS протоколы, нам необходимо найти первый элемент этого связного списка. Фактически это то, что возвращает функция NdisRegisterProtocol в качестве NDIS_HANDLE. Т.е. то что нам необходимо сделать, это зарегистрировать ложный NDIS протокол, сохранить возвращенный указатель и удалить протокол. Это даст нам возможность прохода по списку зарегистрированных NDIS протоколов и обмена существующих указателей на функции на те функции, которыми мы управляем.

Сперва мы регистрируем ложный протокол, для получения указателя. Чтобы быть уверенным в том, что регистрация пройдет успешно, протокол, который мы регистрируем, должен иметь ReceiveHandler:
Код (C++):
  1. NDIS_STATUS DummyNDISProtocolReceive(
  2.   IN NDIS_HANDLE ProtocolBindingContext,
  3.   IN NDIS_HANDLE MacReceiveContext,
  4.   IN PVOID HeaderBuffer,
  5.   IN UINT HeaderBufferSize,
  6.   IN PVOID LookAheadBuffer,
  7.   IN UINT LookAheadBufferSize,
  8.   IN UINT PacketSize)
  9. {
  10.   return NDIS_STATUS_NOT_ACCEPTED;
  11. }
  12.  
  13. NDIS_HANDLE RegisterBogusNDISProtocol(void)
  14. {
  15.   NTSTATUS Status = STATUS_SUCCESS;
  16.   NDIS_HANDLE hBogusProtocol = NULL;
  17.   NDIS_PROTOCOL_CHARACTERISTICS BogusProtocol;
  18.   NDIS_STRING ProtocolName;
  19.  
  20.   NdisZeroMemory(&BogusProtocol,sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
  21.   BogusProtocol.MajorNdisVersion = 0x04;
  22.   BogusProtocol.MinorNdisVersion = 0x0;
  23.  
  24.   NdisInitUnicodeString(&ProtocolName,L"BogusProtocol");
  25.   BogusProtocol.Name = ProtocolName;
  26.   BogusProtocol.ReceiveHandler = DummyNDISProtocolReceive;
  27.  
  28.   NdisRegisterProtocol(&Status,&hBogusProtocol,&BogusProtocol,
  29.   sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
  30.  
  31.   if(Status == STATUS_SUCCESS) return hBogusProtocol;
  32.   else return NULL;
  33. }
Как только мы получили указатель, мы можем отменить регистрацию протокола:
Код (C++):
  1. void DeregisterBogusNDISProtocol(NDIS_HANDLE hBogusProtocol)
  2. {
  3.   NTSTATUS Status;
  4.  
  5.   NdisDeregisterProtocol(&Status,hBogusProtocol);
  6. }
Проходя по списку и переписывая указатели на функции, нам необходимо сохранять старые указатели, чтобы иметь возможность вызывать исходные функции из нашего кода. Есть как минимум 2 способа сделать это:

  1. Создать связный список «перехваченных экземпляров», храня старые указатели для каждого протокола. При вызове наших NDIS-функций этот список должен просматриваться на предмет поиска правильного элемента.
  2. Создавать отдельный экземпляр функции для каждого протокола, который мы перехватываем и записывать старый указатель прямо в код функции. Это требует немного больше усилий, но должно быть быстрее в результате во время выполнения, чем поиск по связному списку. Когда писался этот код, я никогда не задумывался над вторым вариантом, но возможно это то, чем я воспользовался бы сегодня. Так что наслаждайтесь первым вариантом. Он работает нормально и я не замечал каких либо существенных «ударов» по производительности от него.

Для каждого элемента в связном списке зарегистрированных протоколов NDIS, я создам один элемент в своем связном списке и сохраню все необходимые указатели вместе с 2 описателями контекста. Значения описателей будут использованы позже для поиска правильного элемента для текущего протокола. Относительные указатели за тем переписываются таким образом, что указывают на мои версии отсылающих и принимающих функций. Мы также сохраняем указатель на NDIS_OPEN_BLOCK, чтобы проще убирать перехват. Код прохода по списку и перехвата протокола выглядит примерно таким образом:
Код (C++):
  1. NTSTATUS HookExistingNDISProtocols(void)
  2. {
  3.   UINT *ProtocolPtr;
  4.   NDIS_HANDLE hBogusProtocol = NULL;
  5.   PNDIS_OPEN_BLOCK OpenBlockPtr = NULL;
  6.   PNDIS_PROTOCOL_HOOK pNode;
  7.  
  8.   hBogusProtocol = RegisterBogusNDISProtocol();
  9.   if(hBogusProtocol == NULL) return STATUS_UNSUCCESSFUL;
  10.  
  11.   ProtocolPtr = (UINT*)hBogusProtocol;
  12.   ProtocolPtr = (UINT*)((PBYTE)ProtocolPtr + sizeof(REFERENCE) + 8);
  13.   ProtocolPtr = (UINT*)(*ProtocolPtr);
  14.  
  15.   while(ProtocolPtr != NULL) {
  16.   OpenBlockPtr = (PNDIS_OPEN_BLOCK)(*ProtocolPtr);
  17.   if(OpenBlockPtr != NULL) {
  18.   pNode = NewNDISNode();
  19.   if(pNode != NULL) {
  20.   pNode->ProtocolBindingContext = OpenBlockPtr-
  21. >ProtocolBindingContext;
  22.   pNode->MacBindingContext = OpenBlockPtr->MacBindingHandle;
  23.   pNode->OpenBlockPtr = OpenBlockPtr;
  24.   pNode->RealSendHandler = OpenBlockPtr->SendHandler;
  25.   //How about WanSendHandler?
  26.   pNode->RealPostNt31ReceiveHandler = OpenBlockPtr->PostNt31ReceiveHandler;
  27.   OpenBlockPtr->SendHandler = NDISSendHandler;
  28.   //How about WanSendHandler?
  29.   OpenBlockPtr->PostNt31ReceiveHandler = NDISPostNt31ReceiveHandler;
  30.   }
  31.   }
  32.  
  33.   ProtocolPtr = (UINT*)((PBYTE)ProtocolPtr + sizeof(REFERENCE) + 8);
  34.   ProtocolPtr = (UINT*)(*ProtocolPtr);
  35.   }
  36.   DeregisterBogusNDISProtocol(hBogusProtocol);
  37.   return STATUS_SUCCESS;
  38. }
Существует гораздо больше интересных функций в NDIS_OPEN_BLOCK для перехвата, но если вы просто хотите контролировать сетевой трафик, то отсылающих и принимающих функций будет достаточно. Другой нюанс о котором стоит упомянуть, это то что NDIS_OPEN_BLOCK меняется от версии к версии ОС. В ОС Windows 2000 он выглядит иначе по сравнению с ОС Windows XP в основном из-за смены имен членов структуры.

Следующее, что следует сделать теперь, это реализовать передающие и принимающие функции, которые выполняют поиск в связном списке, ища указатели на исходные функции для их вызова, если трафик можно пропустить. Если о трафике необходимо сообщить, то это выполняется до вызова исходной функции. Если предполагается игнорировать трафик, то мы можем просто не вызывать исходную функцию и вернуть соответствующий статус.
Код (C++):
  1.  
  2.  
  3. NDIS_STATUS NDISSendHandler(IN NDIS_HANDLE MacBindingHandle,IN PNDIS_PACKET Packet)
  4. {
  5.   PNDIS_PROTOCOL_HOOK Node Node = FindNDISNode(MacBindingHandle,2);
  6.   if(Node == NULL) return NDIS_STATUS_SUCCESS;
  7.  
  8.   return Node->RealSendHandler(MacBindingHandle,Packet);
  9. }
  10.  
  11. NDIS_STATUS NDISPostNt31ReceiveHandler(
  12.   IN NDIS_HANDLE ProtocolBindingContext,
  13.   IN NDIS_HANDLE MacReceiveContext,
  14.   IN PVOID HeaderBuffer,
  15.   IN UINT HeaderBufferSize,
  16.   IN PVOID LookAheadBuffer,
  17.   IN UINT LookAheadBufferSize,
  18.   IN UINT PacketSize)
  19. {
  20.   PNDIS_PROTOCOL_HOOK Node;
  21.  
  22.   Node = FindNDISNode(ProtocolBindingContext,1);
  23.   if(Node == NULL) return NDIS_STATUS_SUCCESS;
  24.  
  25.   return Node->RealPostNt31ReceiveHandler(ProtocolBindingContext,MacReceiveContext,
  26.   HeaderBuffer,HeaderBufferSize,LookAheadBuffer,LookAheadBufferSize,PacketSize);
  27. }
  28.  
Теперь осталось только одно: снятие перехвата. Это делается путем прохода по нашему связному списку и заменой всех указателей на исходные:
Код (C++):
  1.  
  2. NTSTATUS ReleaseExistingNDISProtocols(void)
  3. {
  4.   PNDIS_PROTOCOL_HOOK CurrentNode;
  5.   PNDIS_OPEN_BLOCK OpenBlockPtr = NULL;
  6.  
  7.   CurrentNode = GetFirstNDISNode();
  8.   if(CurrentNode == NULL) return STATUS_UNSUCCESSFUL;
  9.  
  10.   while(CurrentNode != NULL) {
  11.   OpenBlockPtr = CurrentNode->OpenBlockPtr;
  12.   if(OpenBlockPtr != NULL) {
  13.   OpenBlockPtr->SendHandler = CurrentNode->RealSendHandler;
  14.   OpenBlockPtr->PostNt31ReceiveHandler = CurrentNode-
  15. >RealPostNt31ReceiveHandler;
  16.   }
  17.   CurrentNode = GetNextNDISNode(CurrentNode);
  18.   }
  19.  
  20.   return STATUS_SUCCESS;
  21. }
Что осталось сделать? Код не перехватывает протоколы, которые регистрируются после перехвата NDIS. Выяснение пути для этого оставляется читателю. Код работает? Конечно, я использовал его в win32 версии knockd, названого sesame, который можно найти по адресу .http://www.toolcrypt.org/.

5 3.809
yashechka

yashechka
Ростовский фанат Нарвахи

Регистрация:
2 янв 2012
Публикаций:
90

Комментарии


      1. TermoSINteZ 26 авг 2017
        Еклмн, опрст... (это я заменил знаменитую фразу, чтоб атеисты не батхертили)...
        Яшечка, пожалуйста, не беритесь переводить статьи по сетям - никогда, тем более с драйверами. У вас получается адская каша. Я даже не буду расписывать тут все имеющиеся в этом переводе проблемы....
        Не в обиду, правда, но это жесть .
      2. yashechka 27 авг 2017
        Перевод не мой :)
        Забыл ссылку оставить masters.donntu.org/2008/fvti/dukov/library/hook_ndis_tdi.pdf
      3. dynamic 2 сен 2017
        Может проще свой NDIS драйвер написать и брать на анализ сетевой трафик там же ? + управляющая программа в user mode.
        Приходилось писать под дипломную.
      4. yashechka 2 сен 2017
        Термосин тебе ответит.
      5. RET 7 сен 2017
        Проще перехватывать NtDeviceIoControlFile на уровне AFN
        TermoSINteZ нравится это.