Программирование на C++ глазами хакера

         

Пример работы ТСР-сервера


Начну с разработки сервера. Создайте новый проект Win32 Project с именем TCPServer. Откройте файл TCPServer.cpp и после объявления всех глобальных переменных, но до функции _twinMain, напишите две процедуры из листинга 4.12. Функции должны быть именно в таком порядке: сначала ClientThread, а затем — NetThread.

Листинг 4.12. Функции работы с сетью
DWORD WINAPI ClientThread(LPVOID lpParam) { SOCKET sock=(SOCKET)lpParam; char szRecvBuff[1024], szSendBuff[1024]; int ret; // Запуск бесконечного цикла while(1) { // Получение данных ret = recv(sock, szRecvBuff, 1024, 0); // Проверка полученных данных if (ret == 0) break; else if (ret == SOCKET_ERROR) { MessageBox(0, "Recive data filed", "Error", 0); break; } szRecvBuff[ret] = '\0';

// Здесь можно поставить проверку принятого текста // в переменной szRecvBuffer

// Подготовка строки для отправки клиенту strcpy(szSendBuff, "Command get OK");

// Отправка содержимого переменной szSendBuff клиенту ret = send(sock, szSendBuff, sizeof(szSendBuff), 0); if (ret == SOCKET_ERROR) { break; } } return 0; }

DWORD WINAPI NetThread(LPVOID lpParam) { SOCKET sServerListen, sClient; struct sockaddr_in localaddr, clientaddr; HANDLE hThread; DWORD dwThreadId; int iSize;

// Создание сокета sServerListen = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (sServerListen == SOCKET_ERROR) { MessageBox(0, "Can't load WinSock", "Error", 0); return 0; } // Заполнение структуры localaddr типа sockaddr_in localaddr.sin_addr.s_addr = htonl(INADDR_ANY); localaddr.sin_family = AF_INET; localaddr.sin_port = htons(5050);

// Связывание адреса с переменной localaddr типа sockaddr_in if (bind(sServerListen, (struct sockaddr *)localaddr, sizeof(localaddr)) == SOCKET_ERROR) { MessageBox(0, "Can't bind", "Error", 0); return 1; }

// Вывод сообщения об удачной операции bind MessageBox(0, "Bind OK", "Error", 0);

// Запуск прослушивания порта listen(sServerListen, 4);

// Вывод сообщения об удачном начале операции прослушивания MessageBox(0, "Listen OK", "Error", 0);




// Запуск бесконечного цикла while (1) { iSize = sizeof(clientaddr); // Прием соединения из очереди. Если его нет, // то функция будет ожидать соединения клиента sClient = accept(sServerListen, (struct sockaddr *)clientaddr, iSize); //Проверка корректности идентификатора клиентского сокета if (sClient == INVALID_SOCKET) { MessageBox(0, "Accept filed", "Error", 0); break; }

// Создание нового потока для работы с клиентом hThread = CreateThread(NULL, 0, ClientThread, (LPVOID)sClient, 0, dwThreadId); if (hThread == NULL) { MessageBox(0, "Create thread filed", "Error", 0); break; } CloseHandle(hThread); } // Закрытие сокета после работы с потоком closesocket(sServerListen); return 0; }

Теперь перейдем к рассмотрению написанного. В функции _tWinMain происходит загрузка библиотеки WinSock версии 2.2.

После этого создается новый поток с помощью функции CreateThread. Серверная функция accept блокирует работу программы, и если ее вызвать в основном потоке, то окно заблокируется и не будет реагировать на сообщения. Именно поэтому для сервера создается отдельный поток, в котором он и будет работать. Получается, что основная программа работает в своем потоке, а параллельно ей работает еще один поток, в котором сервер прослушивает необходимый порт.

С точки зрения программирования, поток — это функция, которая будет работать параллельно с другими потоками ОС. Таким образом, в ОС Windows реализуется многозадачность. За более подробной информацией о потоках обратитесь к документации или специализированной литературе по Visual C++.

В качестве третьего параметра функции CreateThread, создающей новый поток, необходимо передать указатель на функцию, которая должна работать в отдельном потоке.

Самое интересное происходит в функции NetThread. Все функции, которые там используются, мы уже рассмотрели, и здесь я только собрал все сказанное в одно целое.

Первым делом создается сокет функцией socket. Затем корректными параметрами заполняется структура localaddr, которая имеет тип sockaddr_in. Для предложенного сервера заполняются три параметра:



locaiaddr.sin_addr.s_addr — указывается флаг INADDR_ANY, чтобы принимать подключения с любого интерфейса, установленного в компьютере;

locaiaddr.sin_family — AF_INET, т.е. интернет-протоколы из семейства используемых протоколов;

locaiaddr.sin_port — порт номер 5050. На большинстве компьютеров он свободен.

После этого связывается заполненная структура с сокетом с помощью функции bind.

Теперь сокет готов к началу прослушивания порта с помощью функции listen. В качестве второго параметра указано число 4, что соответствует очереди из четырех клиентов. Если одновременно попытаются подключиться больше клиентов, то только первые четыре попадут в очередь, а остальные получат сообщение об ошибке.

Чтобы принимать соединения клиентов, запускается бесконечный цикл, в котором и будут обрабатываться все подключения. Почему бесконечный? Сервер должен всегда находиться в памяти и принимать подключения от клиентов в любое время.

Внутри цикла вызывается функция accept, чтобы принять соединение клиента из очереди. Как только соединение произошло, функция создаст сокет и вернет на него указатель, который сохраняется в переменной sClient. Прежде чем использовать новый сокет, его необходимо проверить на корректность. Если переменная sSocket будет равна INVALID_SOCKET, то с таким сокетом работать нельзя.

Если сокет корректный, то запускается еще один поток, в котором уже происходит обмен информацией (чтение данных, которые прислал клиент, ответы на запросы). Поток создается уже знакомой вам функцией CreateThread, а в качестве третьего параметра указывается функция ClientThread, которая и будет работать параллельно основной программе.

В качестве четвертого параметра функции CreateThread можно указывать любой параметр, и он будет передан функции потока. Логично будет указать клиентский сокет, чтобы в функции ClientThread знать сокет, с которым происходит работа.

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



SOCKET sock=(SOCKET)lpParam;

В этой функции также запускается бесконечный цикл на случай, если клиент будет присылать серверу множество команд, и на них надо будет отвечать.

Внутри цикла сначала принимается текст с помощью функции recv. После этого полученные данные проверяются на корректность.

Если контроль прошел успешно, то можно проверить присланную клиентом команду. При написании троянского коня клиент может использовать запросы на высылку паролей, на перезагрузку компьютера, а может и запустить какую-нибудь шутку. Необходимо проверить, какой запрос пришел от сервера, и в зависимости от этого выполнить какие-либо действия.

Запросы могут приходить как простые текстовые команды, например, restart или sendmepassword. Так как мы только разбираем принцип действия троянского коня, но не создаем его, то в примере клиент будет посылать текстовую команду get. Сервер же будет отсылать обратно клиенту текст Command get OK. Текст сохраняется в переменной, содержимое которой и отправляется клиенту с помощью функции send:

strcpy(szSendBuff, "Command get OK");

ret = send(sock, szSendBuff, sizeof(szSendBuff), 0); if (ret == SOCKET_ERROR) { break ; }

Затем цикл повторяется от начала, и если сервер считает еще одну команду, то он ответит на нее, иначе цикл прервется.

Как я уже говорил, все сетевые функции описаны в файле winsock2.h, и его необходимо подключать к своему проекту, чтобы компилятор не выдавал ошибок. В самом начале файла с исходным кодом нашей программы найдите следующею строку:

#include "stdafx.h"

После нее добавьте подключение модуля winsock2.h:

#include winsock2.h

Чтобы собрать проект без ошибок, необходимо подключить библиотеку ws2_32.lib. Для этого щелкните правой кнопкой мыши по имени проекта в окне Solution Explorer и в появившемся меню выберите пункт Properties.

Перед вами откроется окно свойств, в котором надо перейти в раздел Configuration Properties/Linker/Input. Здесь в строке Additional Dependencies напишите имя библиотеки ws2_32.lib.

Вот и все, что относится к серверной программе. Запустив ее, вы должны будете увидеть два сообщения Bind OK и Listen OK. Если сообщения появились, то сервер работает корректно и находится в ожидании соединения со стороны клиента.

Примечание
Исходный код примера, описанного в этом разделе, вы можете найти на компакт - диске в каталоге \Demo\Chapter4\TCPServer.
Но чтобы окончательно протестировать пример, надо написать программу клиента, который будет соединяться с сервером и отправлять ему команды. Именно это я покажу в следующем разделе.


Содержание раздела