Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #132, ДЕКАБРЬ 2009 г.

Тоталитарный контроль трафика. Ловим и контролируем весь TCP/UCP-трафик на компьютере


Мы проводим в интернете море времени, иногда сетуя на злого провайдера и медленный трафик. Зачастую это происходит из-за того, что «левые» или троянские приложения ломятся в сеть за обновлениями и отбирают драгоценный трафик.

У каждого из нас может быть запущено множество процессов — аська (иногда и не одна), торрент, майл-агент и парочка браузеров. Всем им нужна сеть. А если ты сидишь с GPRSмодема? Ой-ой-ой. С помощью материала из этой статьи ты сможешь узнать, как отследить и наказать злобного нарушителя, съедающего твой трафик.

Инструментарий

С целью организации хорошего улова нам понадобится любой С/ С++ компилятор, прямые руки и немного мозгов. Итак, для получения всех соединений мелкомягкие предоставляют набор функций библиотеки iphlplib.dll. Не ко всем компиляторам поставляются актуальные на сегодняшний день заголовочные файлы, поэтому функции будем вызывать по их адресу. Сегодня мы станем использовать пришедшие на смену старым функции GetTcpTable(), GetUdpTable(), GetExtendedTcpTable() и GetExtendedUdpTable(). Кстати, для последних двух необходим, как минимум, Windows XP SP2 (вряд ли это составит для тебя проблему). Функции AllocateAndGetTcpTableFromSt ack() и ей подобные, с таким трепетом рекомендуемые на множестве форумов, я использовать не буду по причине их древности — современными ОС они не поддерживаются. Все наши функции хранятся в библиотеке Iphlpapi.dll, располагающейся в системной директории.

TCP

Функция GetTcpTable() получает таблицу всех Tcp-соединений в системе:

DWORD GetTcpTable(PMIB_TCPTABLE pTcpTable,
PDWORD pdwSize, BOOL bOrder);
typedef DWORD (WINAPI * PROCGETTCPTABLE)
(PMIB_TCPTABLE, PDWORD, BOOL);

Здесь мы объявили прототип этой функции (PROCGETTCPTABLE). Итак, рассмотрим параметры подробнее. Всего их три:

pTcpTable — указатель на выделенный буфер, куда будет записана таблица MIB_TCPTABLE.

pdwSize — размер выделенного буфера; если же буфера будет недостаточно, то в эту переменную запишется размер буфера, нужного для размещения таблицы.

bOrder — если TRUE, то указывает, что полученную таблицу нужно отсортировать по следующим параметрам в порядке:

1. Локальный IP-адрес.
2. Локальный порт.
3. Удаленный IP-адрес,
4. Удаленный порт.

Функция при успехе возвращает NO_ERROR; при ошибке возможно несколько значений, здесь мы рассмотрим только одно:
ERROR_INSUFFICIENT_BUFFER — если буфер, на который указывает pTcpTable, недостаточно большого размера, нужный размер возвращен в значении по адресу pdwSize. Остальные значения, а также более подробное описание функции ты можешь найти в MSDN (msdn.microsoft.com).

UDP

Теперь перейдем к UDP-соединениям. Прототип, согласно MSDN, выглядит так:

DWORD GetUdpTable(PMIB_UDPTABLE pUdpTable,
PDWORD pdwSize, BOOL bOrder);
typedef DWORD (WINAPI * PROCGETUDPTABLE)
(PMIB_UDPTABLE, PDWORD, BOOL);

Про последние два параметра мы уже все знаем (правда, сортировка идет только по IP-адресу и порту), а указатель на структуру имеет тип PMIB_UDPTABLE, так как сама таблица немного отличается.

Структура таблиц

Рассмотрим таблицу, в которую занесутся TCP-соединения, MIB_ TCPTABLE. Она определена ниже и значения ее полей ясны из названия. Отмечу только, что адреса и порты представлены в сетевом формате (старший байт по старшему адресу):

typedef struct _MIB_TCPTABLE
{
DWORD dwNumEntries;
MIB_TCPROW table[ANY_SIZE];
}
MIB_TCPTABLE, *PMIB_TCPTABLE;

Параметр dwNumEntries — количество записей, то есть соединений, а table — массив записей, имеющих вот такой вид:

typedef struct _MIB_TCPROW
{
DWORD dwState;
DWORD dwLocalAddr;
DWORD dwLocalPort;
DWORD dwRemoteAddr;
DWORD dwRemotePort;
} MIB_TCPROW, *PMIB_TCPROW;

Как видишь, тут присутствует состояние соединения (ожидает ли оно подключения, установлено или уже закрывается), локальные и удаленные IP-адрес с портом.

Структуру UDP-таблиц ты можешь найти в MSDN или в приложении к журналу, а пока я приведу код, который покажет все TCP-соединения в системе:

Функция, получающая все TCP-соединения в системе

PMIB_TCPTABLE pTcpTable;
DWORD dwSize = 0;
pTcpTable = (PMIB_TCPTABLE)VirtualAlloc(
NULL, sizeof(MIB_TCPTABLE),
MEM_COMMIT, PAGE_READWRITE);
if (GetTcpTable(pTcpTable, &dwSize, FALSE)
== ERROR_INSUFFICIENT_BUFFER)
pTcpTable = (PMIB_TCPTABLE)
VirtualAlloc(NULL, dwSize,
MEM_COMMIT, PAGE_READWRITE);
GetTcpTable(pTcpTable, &dwSize, TRUE);
printf("Remote Address:Remote Port\tState\n");
for (DWORD i = 0;
i < pTcpTable-> dwNumEntries; i++)
{
printf("%d.%d.%d.%d:%d\t\t%s\n",
LOBYTE(LOWORD(pTcpTable->table[i].dwRemoteAddr)),
HIBYTE(LOWORD(pTcpTable->table[i].dwRemoteAddr)),
LOBYTE(HIWORD(pTcpTable->table[i].dwRemoteAddr)),
HIBYTE(HIWORD(pTcpTable->table[i].dwRemoteAddr)),
ntohs( pTcpTable->table[i].dwRemotePort),lplpszStates[pTcpTable->table[i].dwState— 1]);

Попробую объяснить вышеприведенный исходник. В начале мы выделяем память под указатель, затем вызываем функцию с нулевым dwSize. Удивительно, не правда ли? Это делается с целью узнать актуальный (необходимый) размер структуры, чтобы туда поместился весь список соединений (помнишь? при нехватке места функция возвращает необходимый). Далее мы резервируем память и вызываем функцию. И в цикле печатаем удаленный адрес, порт и состояние соединения, переводя порт и IP из сетевого формата в формат Intel. Вот и все, в прилагаемых файлах смотри полный исходник функции.

Берем сеть под контроль

Вдумчивый читатель (то есть, ты), наверняка, задался вопросом: «как же мы будем контролировать сеть? Ведь мы еще ничего не знаем о самом соединении». Спокойно! Сейчас я познакомлю тебя и с Ex-функциями этого семейства, которые появились на свет, начиная с Windows XP. Эти функции перехватывают многие руткиты и используют все файрволы, поскольку дают нам всю информацию о сети. Вот они:

DWORD GetExtendedTcpTable(
PVOID pTcpTable,
PDWORD pdwSize,
BOOL bOrder,
ULONG ulAf,
TCP_TABLE_CLASS TableClass,
ULONG Reserved);
DWORD GetExtendedUdpTable(
PVOID pUdpTable,
PDWORD pdwSize,
BOOL bOrder,
ULONG ulAf,
UDP_TABLE_CLASS TableClass,
ULONG Reserved);

Помимо открытых портов, функции поддерживают IPv6, позволяют выводить не только имя процесса, породившего соединение, но и имя модуля. Для нас они подходят просто идеально — будем охотиться на зверька, который решил прогуляться по сети.

Эти функции принимают первый параметр как нетипизированный указатель. Почему нетипизированный? Потому что тип структуры, куда занесется результат, заранее не известен и зависит от параметра TableClass. А параметр этот может принимать много значений, в частности, возможна выборка прослушивающих, установленных соединений, всех вместе, а также он может получать ID процесса или даже имя модуля, открывшего соединение. Это справедливо для UDP и TCP модулей. Еще надо учесть, что параметр ulAf принимает беззнаковое целое число, обозначающее получение IPv4 или IPv6 (AF_INET и AF_INET6) соответственно. Но так как IPv6 обладает большим рядом параметров, чем IPv4, то количество типов таблиц будет равно 5 (для IPv6 нельзя получить параметры соединения без PID).

Нас будет интересовать структура типа MIB_TCPTABLE_OWNER_ PID, в нее занесутся параметры соединений и PID каждого процесса.

Структура данных

typedef struct
{
DWORD dwNumEntries;
MIB_TCPROW_OWNER_PID table[ANY_SIZE];
}
MIB_TCPTABLE_OWNER_PID, *PMIB_TCPTABLE_OWNER_PID;
typedef struct
{
DWORD dwState;
DWORD dwLocalAddr;
DWORD dwLocalPort;
DWORD dwRemoteAddr;
DWORD dwRemotePort;
DWORD dwOwningPid;
}
MIB_TCPROW_OWNER_PID, *PMIB_TCPROW_OWNER_PID;

Как мы видим, от структуры MIB_TCPROW эта отличается наличием поля dwOwnigPid. А теперь — приведем отслеживающий шпиона код:

PMIB_TCPTABLE_OWNER_PID pTcpTable;
DWORD dwSize = 0;
pTcpTable = (PMIB_TCPTABLE_OWNER_PID) VirtualAlloc(
NULL, sizeof(MIB_TCPTABLE_OWNER_PID), MEM_COMMIT,
PAGE_READWRITE);
GetExtendedTcpTable(
(PVOID)pTcpTable,
&dwSize,
FALSE,
AF_INET,
TCP_TABLE_OWNER_PID_ALL, 0);
pTcpTable = (PMIB_TCPTABLE_OWNER_PID)VirtualAlloc
(NULL, dwSize, MEM_COMMIT, PAGE_READWRITE);}
GetExtendedTcpTable(pTcpTable, &dwSize,
TRUE, AF_INET, TCP_TABLE_OWNER_PID_ALL, 0);
for (DWORD i = 0; i < pTcpTable->dwNumEntries; i++)
{
printf("%s\t%d.%d.%d.%d:%d\n",
GetProcessNameByID(lpszExeName,
pTcpTable->table[i].dwOwningPid),
LOBYTE(LOWORD(pTcpTable->table[i].dwRemoteAddr)),
HIBYTE(LOWORD(pTcpTable->table[i].dwRemoteAddr)),
LOBYTE(HIWORD(pTcpTable->table[i].dwRemoteAddr)),
HIBYTE(HIWORD(pTcpTable->table[i].dwRemoteAddr)),
ntohs(pTcpTable->table[i].dwLocalPort));
}

Этот кусок кода выведет нам все процессы и места, куда они коннектятся, что позволит тут же выявить нарушителя границы. Функция GetProcessNameByID моя, она определяет имя процесса по его идентификатору, исходник ты сможешь найти во врезке. Кстати, не забудь объявить ее прототип:

typedef DWORD (WINAPI * PROCGETEXTENDEDTCPTABLE)
(PVOID, PDWORD, BOOL, ULONG, TCP_TABLE_CLASS, ULONG);
И где-нибудь (я делаю это в main()) инициализировать указатель:
HMODULE hLib = LoadLibraryA("iphlpapi.dll");
GetExtendedTcpTable = (PROCGETEXTENDEDTCPTABLE)
GetProcAddress(hLib, "GetExtendedTcpTable");

После инициализации функция может быть вызвана как обычная. Не забывай делать всевозможные проверки в коде — они у меня опущены в виду того, что журнал не резиновый. С UDP-подключениями ситуация полностью аналогична, новое будет только в IPv6 — там структура содержит еще и ScopeID-подключения.

Заключение

Полный исходник программы ты можешь найти на DVD, она определяет порты подключений и представляет собой своеобразный, расширенный аналог netstat’a. Как видишь, вся работа очень проста, и ты можешь дополнить программу многими удобными функциями. Например, резолвить DNS (т.е. определять имена хостов, например, через функцию Winsock gethostbyaddr), сделать автообновление, убивать неугодный процесс, задействовать очень полезную опцию «отображать только нелокальные подключения» — ведь многие программы открывают подключения на локалхост, а их нам мониторить не нужно. Графические программы с таким функционалом я встречал, а вот консольных аналогов не видел, надеюсь, она будет тебе полезна. До новых встреч!

Links

Содержание