Сначала несколько слов о том, как зародилась идея написать данную
статью, они же и послужат нам постановкой задачи. Как-то просматривая
форумы rsdn.ru я наткнулся на анонимный пост следующего содержания (да
простит меня неизвестный автор за цитирование, оригинальная лексика
сохранена):
Уже несколько раз у различных провайдеров напарывался на подобные
фразы в договоре:"...Не допускается использование на компьютере
абонента прокси-серверов(WinGate и т.д) или трансляции адресов...".
Тарифы с большим объемом трафика. Собственно, вопросов два — насколько
это законно, и существует ли способ определения того, что используется
NAT. Дома несколько компов и подобные ограничения напрягают.
Задача ставилась интересная, и само собой захотелось найти ей
решение. Собственно анализом и решением данной проблемы мы здесь и
займемся. Вопросы законности мы рассматривать не будем (хотя лично я и
не могу найти разумного
объяснения для подобных требований со стороны провайдера) и ограничимся
только технической стороной вопроса. Мы попробуем замаскировать факт
использования NAT при подключении к Internet небольшой сети. Чтобы не
быть голословными, рассмотрим домашнюю сеть из нескольких компьютеров,
где выход в Internet организован через одну из систем с операционной
системой Windows (использование Unix систем в качестве NAT мы здесь не
рассматриваем), которая непосредственно подключена к Internet. Для
простоты, будем считать, что в качестве NAT используется встроенный в
Windows сервис Internet Connection Sharing (хотя все наши рассуждения
будут верны практически для всех реализаций NAT), подключение к
Internet осуществляется через довольно распространенный (если не самый
распространенный) на данный момент Zyxel USB ADSL модем. Итак, для
начала давайте задумаемся, какую нежелательную информацию несет наш
внешний трафик. Что может позволить провайдеру заподозрить нас в
использовании NAT?
Начинаем разбираться
При самом первом рассмотрении можно указать две самые заметные
сигнатуры присутствующие в заголовке каждого IP пакета, которые выдают
нас с головой даже без использования какого либо сложного анализа.
Достаточно взглянуть на наш внешний трафик в любом сетевом сниффере, и
факт использования NAT можно считать установленным.
Во-первых, это поле числа переходов (TTL) IP пакета. На выходе из
нашей сети пакеты принадлежащие системам находящимся за NAT, будут
иметь значение TTL на единицу меньшее, чем пакеты принадлежащие системе
на которой установлен NAT. Это вполне естественное следствие
выполненной маршрутизации. Таким образом увидев в нашем внешнем трафике
пакеты с разными значениями TTL, провайдер может сделать вполне
законный вывод об использовании маршрутизатора (NAT).
Во-вторых, это поле идентификатора IP пакета. Для каждой системы (по
крайней мере в Windows системах) это поле изменяется независимо, по
очень простому закону: в каждом следующем исходящем IP пакете оно на
единицу больше чем в предыдущем. Таким образом, если во внешнем трафике
отчетливо видны несколько независимых последовательностей
идентификаторов пакетов, то можно сделать вывод, что реально на канале
висит несколько систем, причем ровно столько сколько независимых
последовательностей мы видим.
Это, пожалуй, два самых значительных признака, которые нам предстоит
каким-либо образом скрыть. Однако дело этим не ограничивается. Для
полной анонимности нашей маленькой сети так же желательно замаскировать
механизмы генерации следующих полей заголовков сетевых протоколов
(перечислены по степени значимости):
1) Портов источника для TCP/UDP протоколов (для NAT, как правило используется некий фиксированный диапазон).
2) Initial Sequence Number (ISN) для TCP соединений (про уязвимости
генерации ISN столько уже написано, что повторять все это я даже не
берусь, в нашем случае ISN несут дополнительную информацию о системах
находящихся за
NAT).
3) Идентификаторов и номеров последовательности ICMP пакетов для
следующих типов сообщений: Echo/Echo Reply, Timestamp/Timestamp Reply,
Information Request/Information Reply (механизмы генерации этого
значения могут различаться от системы к системе).
Перечисленные элементы не несут информацию об использовании NAT так
очевидно, как два рассмотренных выше основных признака. Однако же они
несут некоторую дополнительную информацию, которая при более детальном
анализе на большом
объеме трафика позволит выявить закономерности указывающие на
присутствие NAT. Давайте немного порассуждаем о перечисленных признаках
по степени их значимости. Несомненно, самый значимый признак многих
реализаций NAT – это использование определенного фиксированного
диапазона портов для трансляции адресов. Накопив некоторый
объем статистики нашего внешнего трафика можно с уверенностью выделить
этот диапазон. Само его наличие прямо не указывает на использование
NAT, однако найти разумное
объяснение тому факту, что сетевые приложения начинают использовать
порты источника, скажем, после цифры 10000 и практически игнорируют
диапазон 1025-9999 – не так то просто. В случае с Initial Sequence
Number (ISN), опять же накопив определенную статистику по трафику и
зная алгоритм генерации ISN c его привязкой ко времени и при известных
слабостях этого алгоритма можно разделить TCP сессии на группы
принадлежащие к разным системам. Сделать это будет, разумеется,
посложнее чем вычислить диапазон портов в предыдущем случае, однако
теоретически подобная возможность существует и нельзя с уверенностью
сказать, что не существует ее практической реализации. Возможность
утечки информации с ICMP минимальна, этот протокол не так часто
используется, однако если ставить себе задачу закрыть все побочные
источники информации, то и ту дополнительную информацию, которую может
нести в себе ICMP протокол, лучше скрыть.
С основными угрозами нашей приватности мы разобрались, давайте теперь
искать решение. В исходной формулировке задачи мы имеем Windows систему
с поднятым ICS, с ней и будем работать.
Пишем защиту
Для того, чтобы получить возможность изменять заголовки пакетов на
внешнем (Internet) сетевом интерфейсе, нам понадобится драйвер фильтр
пакетов уровня NDIS. Написание подобного драйвера требует навыков
работы в kernel mode и выходит за рамки данной статьи, желающих
разобраться самостоятельно я отсылаю к документации DDK. Мы же
воспользуемся готовым решением, которое позволяет реализовать
прозрачную фильтрацию и обработку пакетов в user mode. Это библиотека
WinpkFilter от ntkernel.com. Продукт сей бесплатный для некоммерческого
использования, скачать run-time библиотеку можно отсюда
http://www.ntkernel.com/w&p.php?id=7.
За основу нашей разработки возьмем пример PassThru из WinpkFilter
SDK и изменим его для модификации описанных выше полей в заголовках IP,
ICMP, TCP и UDP протоколов. Исходный код для получившегося приложения
SafeNat прилагается к данной статье. Здесь же мы рассмотрим его фрагменты и остановимся на некоторых ключевых моментах.
Основной цикл фильтрации пакетов довольно прост и незначительно
отличается от оригинального PassThru. Мы читаем пакеты с указанного в
параметрах интерфейса (это должен быть внешний интерфейс с работающим
NAT), производим необходимые модификации и возвращаем пакеты в стек.
Обработка всех признаков реализована независимо, так что при желании
можно легко варьировать функциональность SafeNat.
while (TRUE)
{
WaitForSingleObject ( hEvent, INFINITE ); // Ждем появления пакетов
ResetEvent(hEvent);
while(api.ReadPacket(&Request)) // Читаем пакеты с интерфейса пока они есть
{
pEthHeader = (ether_header*)PacketBuffer.m_IBuffer;
pIpHeader = (iphdr*)(PacketBuffer.m_IBuffer + ETHER_HEADER_LENGTH);
// Для всех исходящих IP пакетов изменяем идентификатор и TTL
if((ntohs(pEthHeader->h_proto) == ETH_P_IP) && (PacketBuffer.m_dwDeviceFlags == PACKET_FLAG_ON_SEND))
{
ChangeIPID(pIpHeader);
RecalculateIPChecksum(pIpHeader);
}
// Для TCP пакетов модифицируем порт и номер последовательности
if(pIpHeader->ip_p == IPPROTO_TCP)
{
if (PacketBuffer.m_dwDeviceFlags == PACKET_FLAG_ON_SEND)
{
ChangePorts(&PacketBuffer);
ChangeSN(&PacketBuffer);
}
else
{
ChangeSN(&PacketBuffer);
ChangePorts(&PacketBuffer);
}
}
// Для UDP пакетов модифицируем порт
if(pIpHeader->ip_p == IPPROTO_UDP)
{
ChangeUDPPorts(&PacketBuffer);
}
// Для ICMP пакетов модифицируем ICMP ID
if(pIpHeader->ip_p == IPPROTO_ICMP)
{
ChangeICMPID (&PacketBuffer);
}
if (PacketBuffer.m_dwDeviceFlags == PACKET_FLAG_ON_SEND)
{
// Отправляем пакет в сеть от имени TCP/IP
api.SendPacketToAdapter(&Request);
}
else
{
// Отправляем пакет TCP/IP протоколу
api.SendPacketToMstcp(&Request);
}
}
}
Рассмотрим подробнее функции непосредственно связанные с обработкой заголовков пакетов.
1. ChangeIPID – данная функция работает с заголовком IP пакета.
Изменению подлежат поля идентификатора и TTL. С TTL все более или менее
просто, мы выставляем для всех исходящих IP пакетов TTL равный 128.
Таким образом все пакеты имеют один и тот же TTL, что не позволяет
делать какие либо выводы о структуре нашей сети на основе значений TTL
в заголовках IP пакетов и первую угрозу нашей конфиденциальности можно
считать устраненной. Несколько сложнее обстоит дело с идентификаторами
IP пакетов. Эти идентификаторы используются для сборки
фрагментированных пакетов, так что мы должны правильно модифицировать
данное поле присвоив одно и то же значение всем фрагментам пакета. Для
каждого фрагментированного IP пакета мы создаем структуру следущего
вида:
typedef struct ipid
{
unsigned short old_id; /* оригинальный ID */
unsigned short new_id; /* сгенерированный ID */
unsigned short ip_summary; /* текущая обработанная длинна фрагментированного пакета */
unsigned short length; /* полная длинна фрагментированного пакета */
DWORD dwTime; /* метка времени */
} ipid, *ipid_ptr;
Выделение и инициализация этой структуры происходит при обработке
первого полученного фрагмента IP пакета.
Освобождение выделенной памяти происходит либо после обработки
последнего фрагмента IP пакета, либо по тайм ауту (если не все
фрагменты обработаны в течении определенного времени).
void ChangeIPID(iphdr* pIpHeader)
{
ipid_ptr pIpId = NULL;
pIpHeader->ip_ttl = DEFAULT_TTL; // Навязываем всем пакетам TTL = 128
if((ntohs(pIpHeader->ip_off) == IP_DF) // Если в пакете установлен флаг DF (Don’t
Fragment)
||(pIpHeader->ip_off == 0)) // или это первый фрагмент пакета
{
pIpHeader->ip_id = GetID(); // Генерируем новый идентификатор для IP пакета
dprintf (("Diagnostics: New IP ID generated = 0x%X\n", htons(pIpHeader->ip_id)));
}
else
{
// Обработка фрагментов
std::vector<ipid_ptr>::iterator theIterator;
for (theIterator = IdTable.begin(); theIterator != IdTable.end();)
{
if((*theIterator)->old_id == pIpHeader->ip_id)
{
(*theIterator)->dwTime = GetTickCount();
pIpHeader->ip_id = (*theIterator)->new_id; // установка ID для
фрагмента
dprintf (("Diagnostics: New IP ID applied for fragmented packet = 0x%X\n", htons(pIpHeader->ip_id)));
// Подсчет длинны пакета
(*theIterator)->ip_summary += ntohs(pIpHeader->ip_len) - 4 * pIpHeader->ip_hl;
// Проверка на последний фрагмент
if(!(ntohs(pIpHeader->ip_off) & IP_MF))
(*theIterator)->length = (USHORT)(8 * ((ntohs(pIpHeader->ip_off))
& 0x3fff) + ntohs(pIpHeader->ip_len) - 4 * pIpHeader->ip_hl);
if((*theIterator)->length == (*theIterator)->ip_summary)
{
free(*theIterator);
IdTable.erase(theIterator);
}
return;
}
else
{
// освободить ресурсы для для всех фрагментов для которых
// вышел тайм аут
if(GetTickCount() - (*theIterator)->dwTime >= ID_TIME_LIMIT)
{
free(*theIterator);
if((theIterator = IdTable.erase(theIterator)) == IdTable.end())
break;
}
else
theIterator++;
}
}
if(theIterator == IdTable.end())
{
// Создать новый элемент фрагментированного пакета
pIpId = (ipid_ptr)malloc(sizeof(ipid));
ZeroMemory(pIpId, sizeof(ipid));
pIpId->old_id = pIpHeader->ip_id;
pIpHeader->ip_id = GetID();
dprintf (("Diagnostics: New IP ID generated for fragmented packet = 0x%X\n", htons(pIpHeader->ip_id)));
pIpId->new_id = pIpHeader->ip_id;
pIpId->ip_summary = ntohs(pIpHeader->ip_len) - 4 * pIpHeader->ip_hl;
pIpId->dwTime = GetTickCount();
IdTable.push_back(pIpId);
}
}
}
Генерация нового идентификатора для пакета вынесена в отдельную функцию
GetID. Изменяя данную функцию, мы можем задавать произвольный закон
генерации идентификаторов IP пакетов. В простейшем случае это функция
простого инкремента глобальной переменной, последовательно выдающая
значения от 1 до 65535. Генерация идентификатора по такому закону
представляет нашу сеть как одну систему, что в принципе решает
поставленную задачу. В приложении SafeNat применен более сложный закон
генерации идентификаторов с использование линейной реккурентной
последовательности максимального периода (при использовании такой
последовательности каждое значение идентификатора повторяется только
после использования всех остальных значений). В качестве GetID можно
использовать и другие методы генерации идентификаторов затрудняющие
анализ сети.
2. ChangeICMPID – эта функция решает задачу с идентификаторами и
номерами последовательности для ICMP пакетов
следующих типов сообщений: Echo/Echo Reply, Timestamp/Timestamp Reply,
Information Request/Information Request Reply. Для каждого исходящего
ICMP сообщения принадлежащего типам ICMP Echo, Timestamp и Information
request мы выделяем и инициализируем структуру
следующего вида:
typedef struct icmpid
{
u_long sourceIP; /* IP адрес источника
*/
unsigned short old_id; /* Оригинальный ICMP ID */
unsigned short new_id; /* Сгенерированный ICMP ID */
unsigned short old_seq; /* Оригинальный ICMP SEQ */
DWORD dwTime; /* Метка времени */
} icmpid, *icmpid_ptr;
Эта структура нам необходима для выполнения операции восстановления
оригинальных значений идентификатора и номера последовательности при
получении ICMP Echo Reply, Timestamp Reply и Information Request Reply.
Если мы не выполним этого обратного преобразования, то эти ICMP
сообщения будут просто отброшены TCP/IP стеком.
void ChangeICMPID(PINTERMEDIATE_BUFFER pPacket)
{
icmpid_ptr IcmpId = NULL;
iphdr_ptr pIpHeader = (iphdr_ptr)&pPacket->m_IBuffer[sizeof(ether_header)];
icmphdr_ptr pIcmpHeader = (icmphdr_ptr)(((PUCHAR)pIpHeader) + sizeof(DWORD)*pIpHeader->ip_hl);
// Поле идентификатора валидно только для определенных типов ICMP сообщений
if ((pIcmpHeader->type != 8)&&
(pIcmpHeader->type != 0)&&
(pIcmpHeader->type != 13)&&
(pIcmpHeader->type != 14)&&
(pIcmpHeader->type != 15)&&
(pIcmpHeader->type != 16)
)
return;
// Мы не модифицируем идентификатор для исходящих ICMP XXX REPLY сообщений
if (((pIcmpHeader->type == 0)||(pIcmpHeader->type == 14)||(pIcmpHeader->type == 16))&&
(pPacket->m_dwDeviceFlags == PACKET_FLAG_ON_SEND)
)
return;
std::vector<icmpid_ptr>::iterator theIterator;
// Найти существующую ICMP запись для данного пакета
if (pPacket->m_dwDeviceFlags == PACKET_FLAG_ON_RECEIVE)
for (theIterator = IcmpIdTable.begin(); theIterator != IcmpIdTable.end();)
{
if((pIpHeader->ip_dst.S_un.S_addr == (*theIterator)->sourceIP)&&
(pIcmpHeader->id == (*theIterator)->new_id)
)
{
pIcmpHeader->id = (*theIterator)->old_id;
pIcmpHeader->seq = (*theIterator)->old_seq;
(*theIterator)->dwTime = GetTickCount();
RecalculateICMPChecksum(pPacket);
return;
}
else
{
// Освобождаем старые ICMP записи
if(GetTickCount() - (*theIterator)->dwTime >= ICMP_TIME_LIMIT)
{
free(*theIterator);
if((theIterator = IcmpIdTable.erase(theIterator)) == IcmpIdTable.end())
break;
}
else
theIterator++;
}
}
if(pPacket->m_dwDeviceFlags == PACKET_FLAG_ON_SEND)
{
// Выделяем и инициализируем новую ICMP запись для исходящего ICMP пакета
IcmpId = (icmpid_ptr)malloc(sizeof(icmpid));
IcmpId->sourceIP = pIpHeader->ip_src.S_un.S_addr;
IcmpId->old_id = pIcmpHeader->id;
IcmpId->old_seq = pIcmpHeader->seq;
IcmpId->new_id = GetID();
IcmpId->dwTime = GetTickCount();
pIcmpHeader->id = IcmpId->new_id;
pIcmpHeader->seq = pIcmpHeader->id;
dprintf (("Diagnostics: ICMP ID generated = %u. Old = %u.\n", ntohs(IcmpId->new_id), ntohs(IcmpId->old_id)));
RecalculateICMPChecksum (pPacket);
IcmpIdTable.push_back(IcmpId);
}
}
Для генерации новых значений идентификатора и последовательности (в
данном случае мы используем одно и то же значение для обоих полей) так
же используется функция GetID. Аналогично функции ChangeIPID можно
использовать и любой другой подобный генератор.
3. ChangeSN – эта функция ответственна за изменения поля “номер
последовательности” TCP заголовка. При создании нового TCP соединения
функцией GetSNJump генерируется смещение для ISN, таким образом “номер
последовательности”, видимый во внешней сети представляет собой
оригинальное значение увеличенное на величину сгенерированную функцией
GetSNJump. При этом (можно привести математическое доказательство, но
интуитивно это вполне понятно), неопределенность (случайность,
энтропия) нового ISN не меньше неопределенности значений генерируемых
GetSNJump. Можно было бы попросту сгенерировать новый ISN, однако наш
механизм генерации может оказаться хуже оригинального использованного
стеком. При использовании же подхода с генерацией смещения,
неопределенность результирующего значение по крайней мере не хуже
оригинального. Очевидно, чтобы TCP соединения нормально работали,
необходимо обрабатывать пакеты в обоих направлениях. Чтобы все это
работало мы выделяем и инициализируем структуру следущего вида:
typedef struct tcp_state
{
u_long sourceIP; /* IP адрес источника*/
u_long destIP; /* IP адрес назначения */
u_short sourcePort; /* порт источника */
u_short destPort; /* порт назначения */
u_long Jump; /* сдвиг оригинального ISN */
DWORD dwTime; /* метка времени */
} tcp_state, *ptcp_state;
Для исходящих TCP пакетов мы увеличиваем номер последовательности
(Sequence Number) на величину Jump, для входящих уменьшаем номер
подтверждения (Acknowledgement Number) на ту же величину. Память под
структуру освобождается по истечении указанного тайм аута.
Дополнительно, структуру можно освобождать при обнаружении факта
закрытия TCP сессии, однако для простоты реализации в приведенном ниже
коде этого не делается.
void ChangeSN (PINTERMEDIATE_BUFFER pPacketBuffer)
{
iphdr_ptr pIpHeader = NULL;
tcphdr_ptr pTcpHeader = NULL;
ptcp_state pTcpState = NULL;
BOOL bChange = FALSE;
pIpHeader = (iphdr_ptr)(pPacketBuffer->m_IBuffer + ETHER_HEADER_LENGTH);
pTcpHeader = (tcphdr_ptr)(((PUCHAR)pIpHeader) + sizeof(DWORD)*pIpHeader->ip_hl);
// Проверка необходимости создания новой записи для TCP соединения
if( ((pTcpHeader->th_flags == (TH_SYN|TH_ACK)) || (pTcpHeader->th_flags == TH_SYN))
&& (pPacketBuffer->m_dwDeviceFlags == PACKET_FLAG_ON_SEND))
bChange = TRUE;
std::vector<ptcp_state>::iterator theIterator;
// Найти существующую запись для TCP пакета
for (theIterator = TcpTable.begin(); theIterator != TcpTable.end(); )
{
if(((pPacketBuffer->m_dwDeviceFlags == PACKET_FLAG_ON_SEND)&&
(pIpHeader->ip_src.S_un.S_addr == (*theIterator)->sourceIP)&&
(pIpHeader->ip_dst.S_un.S_addr == (*theIterator)->destIP)&&
(pTcpHeader->th_sport == (*theIterator)->sourcePort)&&
(pTcpHeader->th_dport == (*theIterator)->destPort))
||
((pPacketBuffer->m_dwDeviceFlags == PACKET_FLAG_ON_RECEIVE)&&
(pIpHeader->ip_src.S_un.S_addr == (*theIterator)->destIP)&&
(pIpHeader->ip_dst.S_un.S_addr == (*theIterator)->sourceIP)&&
(pTcpHeader->th_sport == (*theIterator)->destPort)&&
(pTcpHeader->th_dport == (*theIterator)->sourcePort)))
{
if(bChange)
{
free(*theIterator);
TcpTable.erase(theIterator);
theIterator = TcpTable.end();
break;
}
// Изменить SEQ или ACK в соответствии с сгенерированным инкрементом
if(pTcpHeader->th_flags & (TH_SYN|TH_ACK))
{
(*theIterator)->dwTime = GetTickCount();
if(pPacketBuffer->m_dwDeviceFlags == PACKET_FLAG_ON_RECEIVE)
{
pTcpHeader->th_ack = htonl(htonl(pTcpHeader->th_ack) - (*theIterator)->Jump);
}
else
pTcpHeader->th_seq = htonl(htonl(pTcpHeader->th_seq) + (*theIterator)->Jump);
RecalculateTCPChecksum (pPacketBuffer);
return;
}
else
return;
}
else
{
// Освобождаем записи старых TCP соединений
if(GetTickCount() - (*theIterator)->dwTime >= TCP_TIME_LIMIT)
{
free(*theIterator);
if((theIterator = TcpTable.erase(theIterator)) == TcpTable.end())
break;
}
else
theIterator++;
}
}
if((theIterator == TcpTable.end()) && (bChange))
{
// Создаем запись для нового TCP соединения
pTcpState = (ptcp_state)malloc(sizeof(tcp_state));
pTcpState->Jump = GetSNJump();
pTcpState->sourceIP = pIpHeader->ip_src.S_un.S_addr;
pTcpState->destIP = pIpHeader->ip_dst.S_un.S_addr;
pTcpState->sourcePort = pTcpHeader->th_sport;
pTcpState->destPort = pTcpHeader->th_dport;
pTcpState->dwTime = GetTickCount();
pTcpHeader->th_seq = htonl(htonl(pTcpHeader->th_seq) + pTcpState->Jump);
dprintf (("Diagnostics: New ISN generated = %u\n", htonl(pTcpHeader->th_seq)));
RecalculateTCPChecksum (pPacketBuffer);
TcpTable.push_back(pTcpState);
}
}
4. ChangePorts/ChangeUDPPorts – эти функции решают последнюю оставшуюся
задачу, они маскируют использование для NAT TCP/UDP портов из
определенного диапазона. Для каждого нового TCP или UDP соединения
выделяется и инициализируется структура следущего вида:
typedef struct tcpports
{
u_long sourceIP; /* IP адрес источника
*/
unsigned short old_port; /* оригинальный порт */
unsigned short new_port; /* новый порт */
DWORD dwTime; /* метка времени
*/
} tcpports, *tcpports_ptr;
для TCP,
typedef struct udpports
{
u_long sourceIP; /* IP адрес источника */
unsigned short old_port; /* оригинальный порт */
unsigned short new_port; /* новый порт */
DWORD dwTime; /* метка времени */
} udpports, *udpports_ptr;
для UDP. Эти структуры используются для трансляции номеров портов в
обоих направлениях. На выходе мы подменяем оригинальный порт источника
значением new_port, на входе восстанавливаем оригинальный порт
назначения используя сохраненное значение old_port.
void ChangePorts(PINTERMEDIATE_BUFFER pPacketBuffer)
{
iphdr_ptr pIpHeader = NULL;
tcphdr_ptr pTcpHeader = NULL;
tcpports_ptr pTcpPorts = NULL;
BOOL bChange = FALSE;
pIpHeader = (iphdr_ptr)(pPacketBuffer->m_IBuffer + ETHER_HEADER_LENGTH);
pTcpHeader = (tcphdr_ptr)(((PUCHAR)pIpHeader) + sizeof(DWORD)*pIpHeader->ip_hl);
// Проверка на создание нового TCP соединения
if ((pTcpHeader->th_flags == TH_SYN)
&& (pPacketBuffer->m_dwDeviceFlags == PACKET_FLAG_ON_SEND))
bChange = TRUE;
std::vector<tcpports_ptr>::iterator theIterator;
// Найдем запись TCP соединения для обрабатываемого пакета
for (theIterator = PortsTable.begin(); theIterator != PortsTable.end();)
{
if(((pPacketBuffer->m_dwDeviceFlags == PACKET_FLAG_ON_SEND)&&
(pIpHeader->ip_src.S_un.S_addr == (*theIterator)->sourceIP)&&
(pTcpHeader->th_sport == (*theIterator)->old_port))
||
((pPacketBuffer->m_dwDeviceFlags == PACKET_FLAG_ON_RECEIVE)&&
(pIpHeader->ip_dst.S_un.S_addr == (*theIterator)->sourceIP)&&
(pTcpHeader->th_dport == (*theIterator)->new_port)))
{
if(bChange)
{
free(*theIterator);
PortsTable.erase(theIterator);
theIterator = PortsTable.end();
break;
}
// Модификация порта в зависимости от направления пакета
if(pPacketBuffer->m_dwDeviceFlags == PACKET_FLAG_ON_SEND)
pTcpHeader->th_sport = (*theIterator)->new_port;
else
pTcpHeader->th_dport = (*theIterator)->old_port;
(*theIterator)->dwTime = GetTickCount();
// Пересчитать контрольную сумму
RecalculateTCPChecksum (pPacketBuffer);
return;
}
else
{
// Освобождение старых записей о TCP сессиях
if(GetTickCount() - (*theIterator)->dwTime >= TCP_TIME_LIMIT)
{
free(*theIterator);
if((theIterator = PortsTable.erase(theIterator)) == PortsTable.end())
break;
}
else
theIterator++;
}
}
if((theIterator == PortsTable.end()) && (bChange))
{
// Создать новую запись для TCP сессии
pTcpPorts = (tcpports_ptr)malloc(sizeof(tcpports));
pTcpPorts->sourceIP = pIpHeader->ip_src.S_un.S_addr;
pTcpPorts->new_port = GetPort();
pTcpPorts->old_port = pTcpHeader->th_sport;
pTcpPorts->dwTime = GetTickCount();
pTcpHeader->th_sport = pTcpPorts->new_port;
dprintf (("Diagnostics: New source port for outgoing connection generated = %u\n", ntohs(pTcpHeader->th_sport)));
RecalculateTCPChecksum (pPacketBuffer);
PortsTable.push_back(pTcpPorts);
}
}
void ChangeUDPPorts(PINTERMEDIATE_BUFFER pPacketBuffer)
{
iphdr_ptr pIpHeader = NULL;
udphdr_ptr pUdpHeader = NULL;
udpports_ptr pUDPPorts = NULL;
BOOL bChange = FALSE;
pIpHeader = (iphdr_ptr)(pPacketBuffer->m_IBuffer + ETHER_HEADER_LENGTH);
pUdpHeader = (udphdr_ptr)(((PUCHAR)pIpHeader) + sizeof(DWORD)*pIpHeader->ip_hl);
// Не меняем значения для портов меньше 1025, так как они могут быть использованы
// локальными сервисами
if ((pPacketBuffer->m_dwDeviceFlags == PACKET_FLAG_ON_SEND)&&
(ntohs(pUdpHeader->th_sport) < 1025))
return;
if ((pPacketBuffer->m_dwDeviceFlags == PACKET_FLAG_ON_RECEIVE)&&
(ntohs(pUdpHeader->th_dport) < 1025))
return;
// Мы не модифицируем UDP фрагменты, кроме первого
if (ntohs(pIpHeader->ip_off) & (~IP_DF) & (~IP_MF))
return;
std::vector<udpports_ptr>::iterator theIterator;
// Ищем UDP запись, соответствующую обрабатываемому пакету
for (theIterator = UDPPortsTable.begin(); theIterator != UDPPortsTable.end();)
{
if(((pPacketBuffer->m_dwDeviceFlags == PACKET_FLAG_ON_SEND)&&
(pIpHeader->ip_src.S_un.S_addr == (*theIterator)->sourceIP)&&
(pUdpHeader->th_sport == (*theIterator)->old_port))
||
((pPacketBuffer->m_dwDeviceFlags == PACKET_FLAG_ON_RECEIVE)&&
(pIpHeader->ip_dst.S_un.S_addr == (*theIterator)->sourceIP)&&
(pUdpHeader->th_dport == (*theIterator)->new_port)))
{
dprintf (("Checking: (*theIterator)-> old_port = %u \n", ntohs((*theIterator)->old_port)));
dprintf (("Checking: (*theIterator)-> new_port = %u \n", ntohs((*theIterator)->new_port)));
dprintf (("Checking: (*theIterator)-> sourceIP = %u \n", (*theIterator)->sourceIP));
dprintf (("Checking: (*theIterator)-> dwTime = %u \n", (*theIterator)->dwTime));
if ((*theIterator)->new_port == (*theIterator)->old_port)
{
// Мы работаем на стороне
сервера, ничего не делаем с пакетом
dprintf (("Diagnostics: Server side port remains unchanged %u \n", ntohs((*theIterator)->old_port)));
(*theIterator)->dwTime = GetTickCount();
return;
}
if(pPacketBuffer->m_dwDeviceFlags == PACKET_FLAG_ON_SEND)
{
dprintf (("Diagnostics: Source port for outgoing packet changed %u
-> %u \n", ntohs(pUdpHeader->th_sport),
ntohs((*theIterator)->new_port)));
pUdpHeader->th_sport = (*theIterator)->new_port;
}
else
{
dprintf (("Diagnostics: Destination port for incoming packet changed %u
-> %u \n", ntohs(pUdpHeader->th_dport),
ntohs((*theIterator)->old_port)));
pUdpHeader->th_dport = (*theIterator)->old_port;
}
(*theIterator)->dwTime = GetTickCount();
RecalculateUDPChecksum (pPacketBuffer);
return;
}
else
{
// Освобождение записей о старых UDP сессиях
if(GetTickCount() - (*theIterator)->dwTime >= UDP_TIME_LIMIT)
{
dprintf (("Deallocating: (*theIterator)-> old_port = %u \n", ntohs((*theIterator)->old_port)));
dprintf (("Deallocating: (*theIterator)-> new_port = %u \n", ntohs((*theIterator)->new_port)));
dprintf (("Deallocating: (*theIterator)-> sourceIP = %u \n", (*theIterator)->sourceIP));
dprintf (("Deallocating: (*theIterator)-> dwTime = %u \n", (*theIterator)->dwTime));
dprintf (("Deallocating: GetTickCount() = %u \n", GetTickCount()));
free(*theIterator);
if((theIterator = UDPPortsTable.erase(theIterator)) == UDPPortsTable.end())
break;
}
else
theIterator++;
}
}
if(theIterator == UDPPortsTable.end())
{
// UDP запись не найдена, создаем новую
if(pPacketBuffer->m_dwDeviceFlags == PACKET_FLAG_ON_SEND)
{
pUDPPorts = (udpports_ptr)malloc(sizeof(udpports));
pUDPPorts->sourceIP = pIpHeader->ip_src.S_un.S_addr;
pUDPPorts->new_port = GetPort();
pUDPPorts->old_port = pUdpHeader->th_sport;
pUDPPorts->dwTime = GetTickCount();
pUdpHeader->th_sport = pUDPPorts->new_port;
dprintf (("Diagnostics: New source port for outgoing UDP connection
generated = %u. Old = %u.\n", ntohs(pUdpHeader->th_sport),
ntohs(pUDPPorts->old_port)));
RecalculateUDPChecksum (pPacketBuffer);
UDPPortsTable.push_back(pUDPPorts);
}
else
{
pUDPPorts = (udpports_ptr)malloc(sizeof(udpports));
pUDPPorts->sourceIP = pIpHeader->ip_dst.S_un.S_addr;
pUDPPorts->new_port = pUdpHeader->th_dport;
pUDPPorts->old_port = pUdpHeader->th_dport;
pUDPPorts->dwTime = GetTickCount();
dprintf (("Diagnostics: Incoming UDP connection for the local server port %u \n", ntohs(pUDPPorts->old_port)));
UDPPortsTable.push_back(pUDPPorts);
}
}
}
Как видно из приведенного кода ChangePorts и ChangeUDPPorts используют
функцию GetPort для генерации нового значения номера порта. В SafeNat
функция GetPort реализована на основе линейной
рекуррентной последовательности максимального периода, из которой
выброшены значения не превосходящие 1024. Это сделано, чтобы избежать
перекрытия с множеством распределенных значений портов используемых для
различных локальных сервисов. Как и в случае с идентификатором IP
пакета, можно применять различные алгоритмы для реализации этой
функции, в том числе и простую функцию инкремента.
Итак, мы получили небольшое приложение (чуть более 1000 строк кода),
которое в значительной степени позволяет скрыть нежелательную
информацию, которая в силу особенностей реализации различных сетевых
протоколов покидает нашу локальную сеть. Области применимости SafeNat и
других подобных приложений не ограничены задачей сокрытия факта
использования NAT, однако мне хотелось бы оставить читателю возможность
подумать над возможными вариантами самостоятельно. В заключение, так же
хотелось бы добавить, что некоторые протоколы уровня приложений так же
могут “светить” внутренние адреса во внешней сети (при этом правильно
работать они скорей всего не будут). Корректная обработка таких
протоколов как правило возлагается на NAT, а самый известный пример –
это протокол FTP, который при работе в активном режиме передает команду
PORT содержащую IP адрес системы и номер порта для открытия канала
передачи данных. Если реализация NAT не поддерживает обработку FTP
протокола (это так же может произойти если FTP запущен на нестандартном
порту), то во внешнюю сеть выйдет пакет содержащий адрес из внутренней
сети, что в нашем случае нежелательно. Для того чтобы избежать
подобного, стоит ограничить диапазон используемых проколов уровня
приложений (например, используя фаервол, открыть выход во внешнюю сеть
только по хорошо известным протоколам/портам поддерживаемым данной
реализацией NAT).
|