SOCKS сервер – очень нужная и полезная вещь. Он служит как бы шлюзом для соединения с удаленным хостом, в случае если прямое соединение невозможно (файрвол) или нежелательно (светиться не хочешь).
Существуют проги, которые служат как бы переходником между обычной, не знающей о существовании SOCKS
программой и SOCKS сервером. Они ловят все пакеты, устанавливают соединение с SOCKS сервером и шлют пакеты на него.
В этой статье рассказано о работе SOCKS сервера и приведена прога, реализующая SOCKS. Вообще, сначала я хотел реализовать SOCKS 5, но понял, что я не совсем понимаю смысл команды UDP ASSOCIATE, а также не могу найти библиотеку, реализующую GSS API под винду.
В общем, читай про нечто среднее – наш сервер будет поддерживать аутентификацию Username/Password, адресацию по имени, а больше ничем от SOCKS 4 отличаться не будет.
Когда клиент начинает подключаться к SOCKS серверу, он шлет ему пакет, содержащий версию SOCKS протокола (в нашем случае 5), количество методов аутентификации, которые он поддерживает и дальше массив идентификаторов методов (подробней читай RFC 1928).
Сервер должен выбрать нужный метод и отправить клиенту ответ, содержащий номер версии и идентификатор выбранного метода.
После этого клиент шлет запрос-команду. Она содержит команду (что сделать), адрес хоста таргета и порта на таргете. Команды бывают CONNECT и BIND (еще бывает UDP ASSOCIATE, но что должен по ней сделать сервак не знаю – глуп). По команде CONNECT сервак должен подключиться к указанному таргету
(по имени или IP), уведомить клиента и наладить передачу данных от клиента к таргету. По команде BIND – почти то же самое, только наоборот: сервак должен открыть указанный порт, уведомить клиента и ждать, пока кто-нибудь туда подключиться, а когда это случится, уведомить клиента еще раз.
Программу я написал на чистом API, поэтому экзешник получился на 26 килобайт, а жатый ASPack’ом – 18,5 килобайт.
Теперь прикинь – запускаешь эту вещь на чужом серваке, ставишь себе
SocksCap и делаешь черные дела абсолютно анонимно. Если запалят IP, то придут туда, где стоит твой сервак (который чужой). А там, понятное дело уже чисто… Ж8-).
///////////////////////////////////////////////////////////////////////////////
// socks.cpp
// Малышев А.В. aka "k41n"
//
#include <windows.h>
#define NOAUTH 0
#define GSSAPI 1//Не реализовано
#define USERNAME_PASSWORD 2
#define AUTHSCHEME USERNAME_PASSWORD
//структура для передачи сокетов другому потоку
struct CONN_PARAM
{
SOCKET sockSession;
SOCKET sockClient;
};
//Функция инициализации сокетов.
bool InitSockets();
//Функция отсылки пакета с ответом, информирующим о ошибке
void SendNegativeResponse(SOCKET sock,int iError);
//Функция отсылки пакета с ответом, информирующим о успехе
void SendPositiveResponse(SOCKET sockSession,SOCKET sockClient);
//Функция потока для установленной связи между удаленным сервером и клиентом нашего SOCKSа
DWORD WINAPI SessionEstablished(LPVOID lpConnParam);
//Функция потока для установления связи между удаленным сервером и клиентом нашего SOCKSа
DWORD WINAPI Session(LPVOID lpParameter);
int main(void)
{
//Инициализировать сокеты
if (!InitSockets())
{
return -1;
}
//Создадим сокет, который будет слушать 31337 Ж8-) порт
SOCKET socketListen=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if (socketListen==INVALID_SOCKET)
{
return -2;
}
//Забиндуем его на 31337 и внешний сетевой интерфейс
sockaddr_in a;
struct hostent *h;
h=gethostbyname("localhost");
h=gethostbyname(h->h_name);
a.sin_family=AF_INET;
a.sin_port=htons(31337);
memcpy(&(a.sin_addr.S_un.S_addr),h->h_addr,sizeof(int));
if (0!=bind(socketListen,(struct sockaddr *)&a,sizeof(a)))
{
return -2;
}
//Начинаем слушать порт
listen(socketListen,50);
//по мере получения запросов на соединение создаем для каждого индивидуальный поток
while (true)
{
sockaddr sa;
int iLength=sizeof(sa);
SOCKET sockSession=accept(socketListen,&sa,&iLength);
if (sockSession==INVALID_SOCKET)
{
return -3;
}
DWORD dwThread;
CreateThread(NULL,0,Session,(LPVOID)sockSession,0,&dwThread);
}
}
//Инициализация сокетов
bool InitSockets()
{
WSADATA wsadata;
if (0==WSAStartup(2,&wsadata)) return true;
else return false;
}
//Послать отрицательный ответ клиенту
void SendNegativeResponse(SOCKET sock,int iError)
{
//Готовим 10 - октетный пакет
char ResponsePacket[10];
//Версия
ResponsePacket[0]=5;
//В зависимости от ошибки придумываем разные причины для отказа Ж8-)
switch (iError)
{
case WSAENETDOWN:
ResponsePacket[1]=3;//Network unreachable
break;
case WSAENETUNREACH:
ResponsePacket[1]=3;//Network unreachable
break;
case WSAECONNREFUSED:
ResponsePacket[1]=5;//Connection refused
break;
case WSAETIMEDOUT:
ResponsePacket[1]=4;//Host unreachable
break;
default:
ResponsePacket[1]=1;//general SOCKS server failure
}
//Этот октет зарезервирован, туда надо 0
ResponsePacket[2]=0;
//Тип адреса
ResponsePacket[3]=1;//IPv4
hostent *h=gethostbyname("localhost");
h=gethostbyname(h->h_name);
//На самом деле соединения не произошло, поэтому никому не интересно,
//На каком IP и порту висит сокет, присоединенный к таргету
*((int *)(ResponsePacket+4))=*((int *)(h->h_addr));
*(short *)(ResponsePacket+8)=htons(4000);
int nReceived;
nReceived=send(sock,ResponsePacket,10,0);
//По инструкции (RFC1928) надо немедленно закрыть сокет
//Мне лень закрывать его корректно, так что просто
closesocket(sock);
}
//Послать положительный ответ клиенту
void SendPositiveResponse(SOCKET sockSession,SOCKET sockClient)
{
//Готовим 10 - октетный пакет
char ResponsePacket[10];
//Версия
ResponsePacket[0]=5;
//Сообщение об успехе
ResponsePacket[1]=0;
//Этот октет зарезервирован, туда надо 0
ResponsePacket[2]=0;
//Тип адреса
ResponsePacket[3]=1;//IPv4
//Определяем IP и порт, на котором висит сокет, присоединенный к таргету
hostent *h=gethostbyname("localhost");
h=gethostbyname(h->h_name);
*((int *)(ResponsePacket+4))=*((int *)(h->h_addr));
sockaddr_in sa;
int iSize=sizeof(sa);
getsockname(sockClient,(sockaddr *)&sa,&iSize);
*(short *)(ResponsePacket+8)=sa.sin_port;
//Шлем пакет
int nReceived=send(sockSession,ResponsePacket,10,0);
if (SOCKET_ERROR==nReceived) //А если ошибка случилась - закрываем сокет. Грубо.
closesocket(sockSession);
}
DWORD WINAPI SessionEstablished(LPVOID lpConnParam)
{
//В этом потоке гоним полученные от таргета октеты
//клиенту
//
CONN_PARAM *pcp=(CONN_PARAM *)lpConnParam;
SOCKET sockSession=pcp->sockSession;
SOCKET sockClient=pcp->sockClient;
int nReceived;
while (true)//Если все идет хорошо, то этот цикл будет выполняться вечно
{
char c;
nReceived=recv(sockClient,&c,1,0);//Получаем данные с таргета
if (SOCKET_ERROR==nReceived) //Если произошла ошибка, закрываем сокет
{
closesocket(sockSession);
return 2;
}
nReceived=send(sockSession,&c,1,0);//Шлем на клиента
if (SOCKET_ERROR==nReceived) //Если произошла ошибка, закрываем сокет
{
closesocket(sockSession);
return 2;
}
}
return 0;
}
DWORD WINAPI Session(LPVOID lpParameter)
{
SOCKET sockSession=(SOCKET)lpParameter;
//Начать переговоры с клиентом
char cVersion;
//Получить версию, которая ему нужна
int nReceived=recv(sockSession,&cVersion,1,0);
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
//Получить методы, которые поддерживает клиент
char cMethodsCount;
nReceived=recv(sockSession,&cMethodsCount,1,0);
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
char *cMethods=new char[cMethodsCount];
for (int i=0;i<cMethodsCount;i++)
{
nReceived=recv(sockSession,&cMethods[i],1,0);
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
}
//Ответить клиенту, послать ему нашу версию и метод 00 - No Auth
switch (AUTHSCHEME)
{
case NOAUTH:
{
char *packet=new char[2];
packet[0]=5;
//Если клиент послал не 5 версию, то шлем ему FF
if (cVersion!=5) packet[1]=(char)0xFF;
else packet[1]=0;
nReceived=send(sockSession,packet,2,0);
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
}
case USERNAME_PASSWORD:
{
char *packet=new char[2];
packet[0]=5;
//Если клиент послал не 5 версию, то шлем ему FF
if (cVersion!=5) packet[1]=(char)0xFF;
else packet[1]=2;
nReceived=send(sockSession,packet,2,0);
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
char cVersion;
nReceived=recv(sockSession,&cVersion,1,0);
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
char cNameLength;
nReceived=recv(sockSession,&cNameLength,1,0);
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
char *sName=new char[cNameLength+1];
nReceived=recv(sockSession,sName,cNameLength,0);
sName[cNameLength]=0;
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
char cPassLength;
nReceived=recv(sockSession,&cPassLength,1,0);
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
char *sPass=new char[cPassLength+1];
nReceived=recv(sockSession,sPass,cPassLength,0);
sPass[cPassLength]=0;
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
if (!strcmp(sName,"Andrew"))
if (!strcmp(sPass,"170838"))
{
char *Packet=new char[2];
Packet[0]=1;
Packet[1]=0;
nReceived=send(sockSession,Packet,2,0);
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
}
else
{
char *Packet=new char[2];
Packet[0]=1;
Packet[1]=0;
nReceived=send(sockSession,Packet,2,0);
closesocket(sockSession);
return 3;
}
else
{
char *Packet=new char[2];
Packet[0]=1;
Packet[1]=0;
nReceived=send(sockSession,Packet,2,0);
closesocket(sockSession);
return 3;
}
}
case GSSAPI:
//Еще не сделано
closesocket(sockSession);
return 4;
}
//Получаем запрос клиента с командой
//Версия
nReceived=recv(sockSession,&cVersion,1,0);
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
//Команда
char cCommand;
nReceived=recv(sockSession,&cCommand,1,0);
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
//Получаем нулевой октет - резервед
char cReserved;
nReceived=recv(sockSession,&cReserved,1,0);
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
//В каком формате будет передан адрес?
char cAddrType;
nReceived=recv(sockSession,&cAddrType,1,0);
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
char *Address;
//В зависимости от того, в каком формате клиент шлет адрес, выделяем на него память и
//получаем
switch (cAddrType)
{
case 1://IPv4
{
Address=new char[4];
nReceived=recv(sockSession,Address,4,0);
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
break;
}
case 3://FQDN - Fully Qualified Domain Name
{
char iLength;
//В первом октете записана длина адреса
nReceived=recv(sockSession,&iLength,1,0);
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
Address=new char[iLength+1];
//Получаем адрес
nReceived=recv(sockSession,Address,iLength,0);
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
Address[iLength]=0;
break;
}
}
short iPort;
//Получаем порт
nReceived=recv(sockSession,(char *)&iPort,2,0);
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
//Обрабатываем запрос
//Создать сокет для соединения к таргету
SOCKET sockClient=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
switch (cCommand)
{
//Команда 1 - CONNECT
case 1:
{
//Соединяемся
sockaddr_in a;
a.sin_family=AF_INET;
a.sin_port=iPort;
//Если адрес дан как FQDN, то надо его отресолвить
if (cAddrType==3)
{
hostent *h=gethostbyname(Address);
memcpy(&(a.sin_addr.S_un.S_addr),h->h_addr,sizeof(int));
}
else
a.sin_addr.S_un.S_addr=*(int *)Address;
//Пробуем подключиться
nReceived=connect(sockClient,(sockaddr *)&a,sizeof(a));
if (nReceived)
{
//Если не вышло, то шлем клиенту отрицательный пакет
SendNegativeResponse(sockSession,nReceived);
return -1;
}
//А иначе шлем клиенту положительный пакет
SendPositiveResponse(sockSession,sockClient);
//Теперь клиент будет слать и получать данные на таргет
//Запустим поток, который будет передавать данные
//от таргета к клиенту
CONN_PARAM cp;
cp.sockClient=sockClient;
cp.sockSession=sockSession;
DWORD dwThreadID;
CreateThread(NULL,0,SessionEstablished,&cp,0,&dwThreadID);
}
break;
//Команда 2 - BIND
case 2:
{
//Создать сокет для соединения к таргету
sockaddr_in a;
a.sin_family=AF_INET;
a.sin_port=iPort;
//Если адрес дан как FQDN, то надо его отресолвить
if (cAddrType==3)
{
hostent *h=gethostbyname(Address);
memcpy(&(a.sin_addr.S_un.S_addr),h->h_addr,sizeof(int));
}
else
a.sin_addr.S_un.S_addr=*(int *)Address;
//Пробуем связать сокет с нужным портом
nReceived=bind(sockClient,(sockaddr *)&a,sizeof(a));
//Если не вышло, то шлем клиенту отрицательный пакет
if (nReceived)
{
SendNegativeResponse(sockSession,nReceived);
return -1;
}
//Пробуем начать слушать
nReceived=listen(sockClient,5);
//Если не вышло, то шлем клиенту отрицательный пакет
if (nReceived)
{
SendNegativeResponse(sockSession,nReceived);
return -1;
}
//А иначе шлем клиенту положительный пакет
SendPositiveResponse(sockSession,sockClient);
//Принимаем входящее соединение от таргета
int iSize=sizeof(a);
sockClient=accept(sockClient,(sockaddr *)&a,&iSize);
//Если не вышло, то шлем клиенту отрицательный пакет
if (sockClient==INVALID_SOCKET)
{
SendNegativeResponse(sockSession,nReceived);
return -1;
}
//А иначе шлем клиенту ВТОРОЙ положительный пакет
SendPositiveResponse(sockSession,sockClient);
}
}
//Создаем поток для пересылки данных от таргета к клиенту
CONN_PARAM cp;
cp.sockClient=sockClient;
cp.sockSession=sockSession;
DWORD dwThreadID;
CreateThread(NULL,0,SessionEstablished,&cp,0,&dwThreadID);
//Теперь в этом потоке мы будем
//гнать данные от клиента к таргету
while (true)//Если все идет хорошо, то этот цикл будет выполняться вечно
{
char c;
//Получаем от клиента
nReceived=recv(sockSession,&c,1,0);
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
//посылаем таргету
nReceived=send(sockClient,&c,1,0);
if (SOCKET_ERROR==nReceived)
{
closesocket(sockSession);
return 2;
}
}
return 0;
}