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

Анализ примера


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

Стоит также заметить, что после каждой операции при работе с сетью происходит проверка на ошибку. Если при создании сокета произошла какая-либо внештатная ситуация, то последующая работа бесполезна.

Давайте рассмотрим, как можно сделать описанный код более универсальным. В примере есть один недостаток. Если одна из сторон должна будет отправить данные слишком большого объема, то они будут отправлены/приняты не полностью. Это связано с тем, что данные уходят маленькими порциями (пакетами), и системный буфер для отправки данных не безграничен.

Допустим, что системный буфер равен 64 Кбайт. При попытке переслать по сети объем данных больше этого значения клиент получит только 64 Кбайт. Остальные данные просто пропадут. Чтобы этого не произошло, вы должны проверять, сколько реально было отправлено, и корректировать ваши действия.

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

Листинг 4.14. Алгоритм отправки данных большого объема
char szBuff[4096]; szBuff = "Данные для отправки..."; int nSendSize = sizeof(szBuff); int iCurrPos = 0;

while(nSendSize 0) { int ret = send(sock, szBuff[iCurrPos], nSendSize, 0); if (ret == 0) break; else if (ret == SOCKET_ERROR) { // Произошла ошибка MessageBox(0, "Send failed", "Error", 0); break; } nSendSize -= ret; iCurrPos += ret; }

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




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

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

Если отправлены еще не все данные, то на следующем шаге функция попытается отправить следующую порцию.

Вы также не сможете и принять сразу большую порцию данных. Поэтому необходимо таким же образом запустить цикл, в котором будет приниматься большая порция данных. Но как определить, насколько велик этот кусок данных? Ведь при отправке известно количество данных, а при приеме — нет.

Решить эту проблему очень просто. Прежде чем отсылать данные, вы должны сообщить принимающей стороне количество байт, которые подлежат пересылке. Для этого должен быть заведомо определен протокол передачи данных. Например, когда приходит команда get, то после нее определенное количество байт можно отвести под значение размера отправляемых данных. Перед самими данными можно отправить команду data. Таким образом, клиент будет знать, сколько ему ожидать данных, и сможет получить их полностью. Код приема данных может выглядеть, как в листинге 4.15.

Листинг 4.15. Алгоритм получения данных большого объема
char szBuff[4096]; int nSendSize = 1000000; int iCurrPos = 0;

while(nSendSize 0) { int ret = recv(sock, szBuff[iCurrPos], nSendSize, 0); if (ret == 0) break; else if (ret == SOCKET_ERROR) { // Произошла ошибка MessageBox(0, "Send failed", "Error", 0); break; } nSendSize -= ret; iCurrPos += ret; }


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