SOCKS сокращение от «SOCKetS» (сокеты, гнёзда) разработан для того, чтобы дать возможность приложениям клиент/сервер в доменах TCP и UDP удобно и безопасно пользоваться услугами межсетевого экрана. Он дает пользователям возможность преодолевать межсетевой экран организации и получать доступ к ресурсам, расположенным в сети Интернет. SOCKS является “посредником уровня приложений”: он взаимодействует с общими сетевыми средствами (например, Telnet и браузер Netscape) и с помощью центрального сервера (прокси-сервера) от имени вашего компьютера устанавливает связь с другими центральными компьютерами.


SOCKS был разработан много лет назад Дейвом Кобласом из компании SGI, и сегодня этот код можно бесплатно получить через Интернет. С момента первого выпуска этот код пережил несколько крупных модификаций, но каждая из них распространялась совершенно бесплатно. SOCKS версия 4 решает вопрос незащищенного пересечения межсетевых экранов приложениями клиент/сервер, основанными на протоколе TCP, включая Telnet, FTP и популярные информационные протоколы, такие как HTTP, Wide Area Information Server (WAIS) и GOPHER. SOCKS версия 5, RFC 1928, является дальнейшим расширением четвертой версии SOCKS. Он включает в себя UDP, расширяет общую рамочную структуру, придавая ей возможность использования мощных обобщенных схем аутентификации, и расширяет систему адресации, включая в нее имя домена и адреса IP v6.

В настоящее время предлагается создать механизм управления входящими и исходящими многоадресными сообщениями IP, которые проходят через межсетевой экран. Это достигается определением расширений для существующего протокола SOCKS V.5, что создает основу для аутентифицированного перехода межсетевого экрана одноадресным пользовательским графиком TCP и UDP. Однако ввиду того, что поддержка UDP в текущей версии SOCKS V.5 имеет проблемы с масштабируемостью и другие недостатки (и их обязательно нужно разрешить, прежде чем переходить к многоадресной передаче), расширения определяются двояко: как базовые расширения UDP и как многоадресные расширения UDP.


Функционирование SOCKS заключается в замене стандартных сетевых системных вызовов в приложении их специальными версиями. Эти новые системные вызовы устанавливают связь с прокси-сервером SOCKS (который конфигурируется самим пользователем в приложении или системным файлом конфигурации), подключаясь к хорошо известному порту (обычно это порт 1080/ТСР). После установления связи с сервером SOCKS приложение отправляет серверу имя машины и номер порта, к которому хочет подключиться пользователь. Сервер SOCKS реально устанавливает связь с удаленным центральным компьютером, а затем прозрачно передает данные между приложением и удаленной машиной. При этом пользователь даже не подозревает, что в канале связи присутствует сервер SOCKS.


Трудность с использованием SOCKS состоит в том, что кто-то должен проводить работу по замене сетевых системных вызовов версиями SOCKS (этот процесс обычно называется “SOCKS-ификацией” приложения). К счастью, большинство обычных сетевых приложений (Telnet, FTP, finger, whois) уже SOCKS-ифицированы, и многие производители включают поддержку SOCKS в свои коммерческие приложения. Кроме того, SOCKS V.5 включает эти процедуры в свою общую библиотеку: на некоторых системах (например, на машинах Solaris) можно автоматически SOCKS-ифицировать приложение, поставив общую библиотеку SOCKS перед “shared libc” в вашей строке поиска библиотек (переменная среды LD__LIBRARY_PATH в системах Solaris).

Анонимность в сети — тема не новая. И вы наверняка устанавливали к себе на комп прогу типа A4Proxy, SocksChain
и им подобные. Лично я не люблю, когда для работы с проксями нужна какая-то отдельная прога. Во-первых
некрасиво, когда много окон на панели задач или значков в трее, во-вторых проги эти требуют кряков, а их
искать лень 🙂 Поэтому я и написал классы для поддержки SOCKS5-серверов, которые я теперь могу заюзать
в какой-нибудь своей проге. И вот теперь хочу всем рассказать, как это делать.

То, к каким серверам и по каким протоколам мы может обращаться через прокси, зависит от
типа этого прокси, т. е. протокола, по которому мы обращаемся к нему. Типов проксей существует нескольно:
HTTP-proxies, SOCKS4, SOCKS5, SSL CONNECT и т.д. HTTP-proxy наиболее распространены, их легче всего найти и инете, но работают они только с HTTP, к тому
же могут вставлять в заголовки запроса адрес клиента, то есть быть
не анонимными. Протокол SOCKS наиболее примечателен тем, что он инкапсулирует протоколы не прикладного, а
транспортного уровня, т.е. TCP/IP и UDP/IP. Поскольку только по этим протоколам возможна работа в Сети,
через SOCKS можно работать с любыми серверами, в том числе и такими же SOCKS и,
таким образом, организовывать цепочки SOCKS-серверов. По этой же причине ВСЕ SOCKS-сервера анонимны — невозможно
на уровне TCP/IP и UDP/IP передать дополнительную информацию, не нарушив работу вышестоящего
протокола.

Мы остановимся на протоколе SOCKS5. Его описание лежит в
. Для SOCKS5 стандартным является порт 1080, но, впрочем, на этот
стандарт никто особого внимания не обращает. Каждое SOCKS-соединение проходит стадию аутентификации, если она требуется, затем клиент
посылает команду. Команда может быть одна из трех:
CONNECT — исходящее TCP-соединение с указанным адресом. Использование этой команды мы рассмотрим
подробнее, так как она нужна наиболее часто. BIND — открыть порт (сервер выбирает порт и посылает клиенту адрес и порт) и принять TCP соединение.
Серверу может понадобится знать, кто будет соннектиться. На этот случай нужно передать эту инфу. UDP ASSOCIATE — открыть UDP-порт (сервер выбирает порт). Данные, предназначенные для конечного
хоста и данные от него идут тоже по UDP. Данные в SOCKS5 передаются в бинарном виде, а не в текстовом, как в HTTP, SMTP, POP3 и др.

Описание протокола

Сконнектившись с сервером, клиент шлет пакет, в котором указана версия протокола и поддерживаемые
методы аутентификации. Этот пакет имеет следующий формат:

BYTE Version;
BYTE nMethods;
BYTE methods

Версия должна быть 5. Каждый элемент methods определяет не только метод аутентификации, но и способ шифрования данных,
если оно используется. Из этих методов сервер выбирает один. Можно указывать любое количество методов, но, если сервер не требует аутентификации, то никакие методы,
кроме 0x00 (не использовать ни аутентификацию, ни шифрование) не потребуются. В ответ сервер шлет пакет следующего содержания:

BYTE Version
BYTE method,

где method — выбранный сервером метод или 0xFF (ни один из предложенных методов не поддерживается). Если метод 0x00, то можно сразу посылать команду.

Пакет команды имеет следующий формат:

BYTE Version; // 5
BYTE Cmd ; // 1 — CONNECT
BYTE Reserved; // 0

BYTE addr;
WORD port; // Байты в сетевом порядке, т. е. htons(Port);

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

Сервер посылает ответ:

BYTE Version; // 5
BYTE Rep ; // 0 — Ok
BYTE Reserved; // 0
BYTE AType; // 1 — IPv4; 3 — domain name; 4 — IPv6
BYTE addr;
WORD port;

Здесь адрес и порт — это адрес и порт, видимые хосту. Возвращается, как правило, IP-адрес, а не доменное
имя. Этот адрес может отличаться от того, по которому мы обращаемся к серверу, особенно, если сервер
используется по назначению, т. е. для выхода из локалки в инет. Если Rep не ноль, т. е. ошибка, то закрываем соединение, в
противном случае работаем с хостом. Мы не используем шифрование, поэтому просто передаем и принимаем данные, как при обычном соединении. Если одна из сторон закроет соединение с socks-сервером, то он сразу же закроет соединение с другой
стороной. Одно socks-соединение инкапсулирует одно TCP-соединение или попытку его установления,
так что если использовать socks для анонимного сканирования портов, то эта
процедура может занять пол дня.

Кодинг

Поскольку socks инкапсулирует TCP, целесообразно сделать класс socks-соединения производным от
класса сокета, но MFCшный CSocket не подходит, т.к. у него все методы
не виртуальные. Напишем свой класс сокета и назовем его, скажем, CTSocket

#include

class CTSocket
{
public:





virtual void Close();
virtual unsigned long GetHost(); // Узнать свой адрес. Это тоже может понадобиться.

private:
SOCKET sock;
};

Реализацию этого класса каждый сможет написать сам (кто не знает как, RTFM MSDN), так что не буду ее
рассматривать. Теперь напишем класс socks-соединения. Он будет поддерживать только самый необходимый набор
функций: поддерживается только команда CONNECT, не поддерживается аутентификация и SOCKS-сервер
задается только IP-адресом, а не доменным именем. Больше в одной статье не поместится.

Class CSocksSocket: public CTSocket
{
public:
virtual BOOL CreateSocket();
virtual BOOL Connect(unsigned long ip, unsigned short port);
virtual BOOL Connect(LPCSTR name, unsigned short port);
virtual int Send(const char* str, int len);
virtual int Recv(char* buf, int max);
virtual BOOL Close();
virtual unsigned long GetHost();

CTSocket* pSocket;
unsigned long socks_ip;
unsigned short socks_port;

private:
char buffer; // Такого размера точно хватит
unsigned long l_ip; // Адрес, возвращаемый функцией
GetHost()

};

// Реализация
BOOL CSocksSocket::CreateSocket()
{
if (!pSocket->CreateSocket()) return FALSE;
if (!pSocket->Connect(socks_ip, socks_port)) return FALSE;
buffer = 5; // Ver
buffer = 1; // 1 method
buffer = 0; // no auth
pSocket->Send(buffer, 3);
int n = pSocket->Recv(buffer, 2);
if (n != 2) return FALSE;
method 0 not supported
return TRUE;
}

BOOL CSocksSocket::Connect(unsigned long ip, unsigned short port)
{
buffer = 5; // Ver
buffer = 1; // CONNECT
buffer = 0; // Reserved
buffer = 1; // IPv4
*((unsigned long*)(buffer + 4)) = ip;
*((unsigned short*)(buffer + 8)) = port;
pSocket->Send(buffer, 10);
int n = pSocket->Recv(buffer, 10);
if (n != 10) return FALSE;
if (buffer != 0) return FALSE; //
Can’t connect

return TRUE;
}

BOOL CSocksSocket::Connect(LPCSTR name, unsigned short port)
{
buffer = 5;
buffer = 1;
buffer = 0;
buffer = 3; // Domain name
int m = strlen(name);
buffer = m; //
Length byte
memcpy(buffer+5, name, m); //
Копируем строку без завершающего нуля
*((unsigned short*)(buffer + 5 + m)) = port;
pSocket->Send(buffer, m + 7);
int n = pSocket->Recv(buffer, 10);
if (n != 10) return FALSE;
if (buffer != 0) return FALSE;
if (buffer != 1) return FALSE; //
Будем требовать, чтобы нам сказали IP, а не что-нибудь другое.
l_ip = *((unsigned long*)(buffer + 4));
return TRUE;
}

int CSocksSocket::Send(const char* str, int len)
{
return pSocket->Send(str, len);
}

int CSocksSocket::Recv(char* buf, int max)
{
return pScoket->Recv(buf, max);
}

void CSocksSocket::Close()
{
pSocket->Close();
}

unsigned long CSocksSocket::GetHost()
{
return l_ip;
}

// Ну, а теперь тестовая прога
void main()
{
WSADATA wsadata;
CTSocket tsock;
CSocksSocket ssock(&tsock);

WSAStartup(MAKEWORD(2,2), &wsadata);

ssock.socks_ip = inet_addr(«10.10.10.10»); // Впишите сюда нужный адрес
ssock.socks_port = 1080; //
Впишите сюда порт

if (!ssock.CreateSocket()) return; // Can’t connect to socks
// or auth required
if (!ssock.Connect(«www.mail.ru», htons(80))) return; //
www.mail.ru
// is inaccessible
LPSTR q = «HEAD / HTTP/1.1\xD\xAHost: www.mail.ru:80\xD\xAUser-Agent: xakep\xD\xA\xD\xA»;
ssock.Send(q, strlen(q));

char buf;
int n = ssock.Recv(buf, 1000);
buf[n] = 0;
printf("%s", buf);