Карта сайта Хакер в RSS Энциклопедия Хакера PDA версия сайта Почтовые рассылки Хакера    Хакер в Twitter Хакер в ВКонтакте Приложение Хакер для Facebook Хакер на Formspring.me
Журнал Новости Форум Видео Life Xakep Live (блоги)
Bugtrack Статьи Блог Поиск English
Взлом как образ мысли Взлом как образ мысли
Интервью с человеком, который, как оказалось, является не только талантливым пентестером в одной из крупных ИБ-компании, но и хакером-ветераном, который уверенными шагами вышел на свет и прикоснулся к истории российской хак-сцены....
Полный гид по накрутке онлайн-голосований Полный гид по накрутке онлайн-голосований
Конкурсы с голосованием привлекают большое количество посетителей, а трафик, как известно, — это деньги. Особый интерес вызывают конкурсы, где за победу предлагаются лакомые призы....

Конкурс: Как скрыть NAT от провайдера

Bookmark and Share

Скрыть прокси от провайдера

Введение

Сначала несколько слов о том, как зародилась идея написать данную статью, они же и послужат нам постановкой задачи. Как-то просматривая форумы 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).





СЛЕДУЮЩИЕ СТАТЬИ
Filter::Decrypt: расшифровка вслепую
ОБСУЖДЕНИЕ СТАТЬИ
Логин:
Пароль:
Если у вас есть форумный логин - вы можете использовать его, иначе анонимный гостевой доступ.

Для оставления комментария вы можете зарегистрироваться по упрощенной процедуре.

Обсуждение этой статьи на forum.xakep.ru
Для отправки сообщения введите код, указанный на картинке
Сообщение

UserГость
15.04.2007 8:56:02
Ответить Ссылка
Работа проведена огромная... но данный метод неэффективен с юридической точки зрения, так как существует большое количество продуктов, которые меняют приведенные выше значения с целью сокрытия информации о версии операционной системы. Пусто провайдер потом докажет, что у меня NAT... может я использую именно один из таких продуктов. Кстати говоря, вингейт, как и максарадинг при помощи IPchains в открытом виде передает ip адрес и порт отправителя. с обычным натом ниодин провайдер справиться не в состоянии, так как всегда можно опровергнуть юридически. но работа, а именно программная часть, очень понравилась! твердая тройка
UserГость
15.04.2007 8:56:02
Ответить Ссылка
Wow ! Пожалуй вполне может хватать изменения TTL
UserГость
15.04.2007 8:56:02
Ответить Ссылка
Мда... хотелось бы добавить пару комментариев: >предыдущему комменчику%) с юридической точки зрения практически любой провайдер может взять и отрубить тебя с возвратом денег (договор на услуги надо читать) >>автору полное сокрытие невозможно, можно НАТ обнаружить по логам любого веб-сервера (по используемым браузерам) ICS, поднятый на одном из двух интерфейсов, глядящих в одну сеть неизбежно влечет полную маршрутизацию пакетов из сети в инет, что можно заблочить лишь фаерволом, и что можно легко пропалить. к тому же, если пров видит, что от тебя ходят пакеты с ттл 128 (т.е. винда) а IPID генерируется рандомом (а не +1), то это вполне наводит на странные мысли =)
UserГость
15.04.2007 8:56:02
Ответить Ссылка
Автору предыдущего комментария: 1) Было упомянуто, что протоколы уровня приложений могут вести к утечке информации. Хотя мне кажется, что пример с веб сервером и анализом его логов несколько притянут за уши (и справедлив только для больших разнородных сетей), например, в своей домашней сети я использую только один браузер на всех системах. Хотя никто не может запретить мне юзать IE и Firefox на одном компе. В конце концов, можно так же пропарсить HTTP пакеты и прописать везде одного агента принудительно, если сеть действительно настолько разнородна. 2) То что фаервол использовать необходимо, тоже отмечено, случайные утечки внутренних адресов в сетевых пакетах нежелательны. Да и кто сейчас подключается к Internet без фаервола. 3) Что касается IP ID то более сложный вариант с рандомизацией приведен скорее для общности, в статье отмечено что можно использовать и простой инкремент. Программа в исходниках, изменение минутное дело.
UserГость
15.04.2007 8:56:02
Ответить Ссылка
Отличная статья, 5+, сегодня же попробую.
UserГость
15.04.2007 8:56:02
Ответить Ссылка
Если не облом - обьясните, плиз, какая провайдеру фиг разница, сколько у вас там машин стоит? Деньги-то за трафик по-любому будут проплачены. Или я чего-то не понимаю?
UserГость
15.04.2007 8:56:02
Ответить Ссылка
Ну допустим тариф без огрничения трафика, тогда сеткой мы можем нагружать канал поплотнее чем одним компом...
UserГость
15.04.2007 8:56:02
Ответить Ссылка
про нагрузку траффика - неубедительно. У меня в свое время один комп работал и ежедневно была пауза в 2 часа, а все остальное время канал грузился на 90-95 процентов.
UserГость
15.04.2007 8:56:02
Ответить Ссылка
Насчет какая разница: если тариф безлимит, то даже при том что траффик по сути такой же, пров получает меньше денег - ведь он теоретически мог бы получать аб.плату с каждого кто за NAT, подключись они по отдельности. С траффиком история другая, но суть та же, из-за принципа "больше предоплата - дешевле траффик".
UserГость
15.04.2007 8:56:02
Ответить Ссылка
Тогда объясните мне, чего ради Zyxel рекламирует свои Omni ADSL WLAN, если, как вы говорите, провайдеры резко против подобной акробатики?
UserГость
15.04.2007 8:56:02
Ответить Ссылка
На машинах за NAT в добавляем DefaultTTL = 129, а про IPID - это ваще вилами по воде....
UserГость
15.04.2007 8:56:02
Ответить Ссылка
добавлять сюда: HKLM/System/CurrentControlSet/Services/Tcpip/Parameters
UserГость
15.04.2007 8:56:02
Ответить Ссылка
http://www.stream.ru/oferta/app3/ 2.7.
UserГость
15.04.2007 8:56:02
Ответить Ссылка
а если у меня на машине стоит эмулятор, ну какая-нить виртуальная машина и я через нее, как шлюз выхожу. тоже ттл +1 но ведь я не предоставляю 3 лицу интернет. как тут быть
UserГость
15.04.2007 8:56:02
Ответить Ссылка
Какой ужос :))))) Я так вообще могу своему прову позвонить и меня там даже проконсультируют как раутер настроить :)
Avatarzerkalik
26.04.2007 17:22:21
Ответить Ссылка
Мдаааааа.....
статья 5+++++ почерпнул много интересного ... хотя с подобной проблемой не сталкивался (но слышал) На Украине, таких приколов провайдеры не выкидывают (национальные Укртелеком, Оптима) мало того при установке самого АДСЛ есть два тарифа первый (чуть больше) с выездом мастера который все настроит, причем мастера без проблем подымают АДСЛ как роутер и сами втыкивают его в локалку; второй вариант (тариф чуть меньше) тебе просто дают АДСЛ и лог и пароль и настраивай его сам ... как хочешь хоть роутером хоть бриджем ...
О проблеме ставшей поводом для написания статьи я слышал только в рамках провайдеров предоставляющих услуги посредством локалок протянутых по жилым домам .... там конечно все сложно ... внутрений трафик стоит как правило копейки а вот внешний (да ещё после превышения пакета) мама не горюй вот там вот провайдеры и пытаются воевать но как правило юзеры(даже и не очень умные) побеждают.... так как в таких "шарашкиных конторах" работают далеко не самые умные и опытные админы... и как правило прекрасно пролазит банальные решения в виде "вингейт" или "юсергейт".... кстате видел вариант при котором был поднят ВПН между машинами а уж из ВПН был реализован роутинг в инет ... конечно не выход но можно всегда начать кричать провайдеру что у тебя есть ноутбук и ты просто подымаешь локалку со своей-же домашней тачкой ......
Кстате опыт показывает что с админами провайдеров можно не только дружить но и успешно договариваться ... они тоже люди и тоже любят пиво а уж о их лени сложено множество легенд
UserГость
06.06.2007 18:08:54
Ответить Ссылка
Хотелось бы увидеть реализацию на перле, всеж для этих целей он подходит больше
UserГость
29.06.2007 19:28:43
Ответить Ссылка
А как сделать такое же на linux?
UserГость
08.07.2007 1:54:29
Ответить Ссылка
Да, статейка интересная...

А вообще у меня, в Эстонии, аналогичная проблема была,
но решили её проще - сменили провайдера.
И качество связи повысилось и головной боли меньше стало:
нормального провайдера наличие внутренней сети совсем не волнует.
UserГость
17.08.2007 16:02:26
Ответить Ссылка
а если используется железка для такиз целей?
Например, маршрутизатор D-LINK 60*.
Видна ли будет прову информация?
UserГость
20.10.2007 0:28:59
Ответить Ссылка
Статья действительно отличная. Но мой пров не только определяет, но и спокойно блокирует весь траф идущий через НАТ, в том числе с запущеной утилиткой которая должна его скрывать.
UserГость
15.11.2007 16:54:52
Ответить Ссылка
епт! Сильная информация! Может пригодиться в мирных целях. Спасибо Вадим!

--
С уважением.
Васильев Владимир. www.c800h.com
UserГость
21.01.2008 1:39:34
Ответить Ссылка
в линуксе делается все при помощи iptables
UserГость
21.01.2008 1:41:35
Ответить Ссылка
Мне кажется описанный способ уж очень сложен. Для чего такой огород городить.
Linux думаю с этим прекрасно справится при помощи iptables
UserГость
28.05.2008 11:29:36
Ответить Ссылка
Господа, подскажите чем мне скомпилить этот сурс?
UserГость
29.07.2008 21:54:38
Ответить Ссылка
Visual Studio
UserГость
18.08.2008 13:58:09
Ответить Ссылка
Пункт про НАТ включен в договор, чтобы избежать перепродажи клиентом трафика третьим лицам. Типа, один подключился, на всю 16-этажку раскинул локалку и цедит им инет за денежку. Если провайдер нормальный (в смысле, люди там нормальные), то Вы ему всегда объясните и покажете, что у вас, к примеру, комп дополнен NAS-сервером и ноутбук коннектится через Wi-Fi. Нет перепродажи - пров спокоен. И ловить он будет не по ТТЛ, а по трафику, зашкаливающему до предела 24 часа в сутки. Там фильтры не помогут, отрубят наглухо без всяких доказательств.
UserГость
14.12.2008 12:52:53
Ответить Ссылка
зачем писать прогу на каком-то быдло-говне под венду если в iptables все эти функции есть с незапамятных времен. А если и нет то есть куча альтернативных модулей.
UserГость
10.02.2009 14:33:19
Ответить Ссылка
> чтобы избежать перепродажи клиентом трафика третьим лицам.
> Типа, один подключился, на всю 16-этажку
> раскинул локалку и цедит им инет за денежку.
Что мешает поставить для этого на шлюзе обычный proxy и не использовать NAT ?
UserГость
15.02.2009 23:04:40
Ответить Ссылка
А затем что если перепродаешь трафффииггг
Пользователи хотят инет без гемора а не прописывать твою проксю в каждой проге.
а еще есть TOR, тоже неплохо, еще и шифрует, а то вдруг пров снифануть канал захочет, и сразу просекет сколько ты почтовых акков держишь, только блин через TOR скорость оставляет желать лучшего :(
UserГость
02.07.2009 9:33:55
Ответить Ссылка
а если у меня локалка за Cisco ASA 5510?
UserГость
16.10.2009 16:22:39
Ответить Ссылка
насчет перепродажи трафа +1

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

Но это противоречит закону о защите прав потребителей. Не поверите, но провы тоже боятся этого закона.
Статья для меня слишком уж перегружена техническими терминами, но наверное содержит неплохое описание решения проблемы. Я - не заморачивась, поставил хендикеш и раздаю нет на свой бук. все. Да и еще на бук жены. Даже если она далеко от дома. У нас в городе есть беспроводная локалка и так многие делают.
Avatarrgo
16.10.2009 17:09:54
Ответить Ссылка
quote:

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

Это прова зависит. Может он, запрещая NAT, надеется на то, что ты купишь второй ip, и пригласишь его монтёров, которые впарят тебе роутер и проводку по дому протянут. А может пров просто страхуется, чтобы в случае перепродажи трафика, не заморачиваться с доказательством факта перепродажи, но прикрыть конторку обоснованно.
UserГость
16.10.2009 17:34:25
Ответить Ссылка
>стра****тся

какой интересный способ писать слово "страхуется" :-)
Avatarrgo
16.10.2009 18:19:47
Ответить Ссылка
quote:

ORIGINAL: Guest
>стра****тся
какой интересный способ писать слово "страхуется" :-)

Да, действительно. Не обратил сразу внимания. ))
UserГость
18.10.2009 2:20:52
Ответить Ссылка
ну прямо таки очень не терпится привести один абзац из мана, на счет линя (iptables,таблица mangle)
"Действие TTL используется для установки значения поля TTL (Time To Live) пакета. Есть одно неплохое применение этому действию. Мы можем присваивать определенное значение этому полю, чтобы скрыть наш брандмауэр от чересчур любопытных провайдеров (Internet Service Providers). Дело в том, что отдельные провайдеры очень не любят когда одно подключение разделяется несколькими компьютерами. и тогда они начинают проверять значение TTL приходящих пакетов и используют его как один из критериев определения того, один компьютер "сидит" на подключении или несколько."
UserГость
15.03.2010 22:28:57
Ответить Ссылка
Ребята подскажите как это дело компильнуть, чет у меня не получается, много неувязок в фильтре находит




Keywords: zPOSTz zHOMEz, zHACKz, zOTHERHACKz, zSOFTz, zHOWz, zINFOz z29448z
Для Авторов: edit Lock delete Lock



    Rambler's Top100