Разбираемся с SOCKS4

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

Протокол socks4 позволяет проксировать любые tcp-соединения, таким образом поддерживая весь стек протоколов уровня приложения (HTTP\HTTPS\FTP\XMPP\TELNET и тд) .

Разберём протокол по косточкам

Чтобы увидеть как все это устроено вам понадобится снифер, например snpa, хотя подойдет любой другой.

Чтобы использовать прокси-сервер мы должны подключится к нему на нужный порт и послать запрос клиента:

  • поле 1: номер версии SOCKS, 1 байт
  • поле 2: код команды, 1 байт:
    • 0x01 = установка TCP/IP соединения
    • 0x02 = назначение TCP/IP порта
  • поле 3: номер порта, 2 байта
  • поле 4: IP-адрес, 4 байта
  • поле 5: ID пользователя, строки переменной длины, завершается null-байтом

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

Сервер возвращается нам следующую структуру

  • поле 1: null-байт
  • поле 2: код ответа, 1 байт:
    • 0x5a = запрос предоставлен
    • 0x5b = запрос отклонён или ошибочен
    • 0x5c = запрос не удался, потому что не запущен identd
    • 0x5d = запрос не удался, поскольку клиентский identd не может подтвердить идентификатор пользователя в запросе
  • поле 3: 2 произвольных байта, должны быть проигнорированы
  • поле 4: 4 произвольных байта, должны быть проигнорированы

Давайте посмотрим на практике:

04 01 00 50 05 FF FF 05 00
00 5A 00 00 00 00 0000
GET / HTTP/1.1
Host: yandex.ru
Connection: keep-alive
Accept-Encoding: gzip, deflate, sdch
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4


HTTP/1.1 302 Moved temporarily
Location: http://www.yandex.ru/
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Length: 0

Мы посылаем..

04номер версии SOCKS
01установка TCP/IP соединения
00 50номер порта (0x50 = 80 порт в десятичной)
05 FF FF 05IP-адрес (5.255.255.55 )
00пустой ID пользователя

И получаем от сервера:

00null-байт
5Aзапрос предоставлен
00 002 произвольных байта
00 00 00 004 произвольных байта

Дальше мы посылаем запрос к хосту яндекса, так же как бы мы делали это без-прокси, обычный GET-запрос.

Время накодить свой чекер!

Писать мы будем (тут и далее) на c, ибо многим нужно отучаться от устаревших языков типа делфи или VB.

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

Для того чтобы не искать прокси, будем использовать торовские, как их настроить можно прочесть здесь.

Полный код программы

#include "stdafx.h"

BOOL WsaInit();
SOCKET ConnectToSocks4(char* proxy_addr, WORD proxy_port, char* dest_addr, WORD dest_port);

void entry()
{
	if (WsaInit())
	{
		SOCKET s = ConnectToSocks4("127.0.0.1", 9050, "yandex.ru", 80);
		
		//lets send GET-request
		char* get = "GET / HTTP/1.1\r\nHost: yandex.ru\r\nUser-Agent: Mozilla/5.0\r\n\r\n";
		if (send(s, get, lstrlen(get), 0) != INVALID_SOCKET)
		{
			char answer[1024];
			if (recv(s, answer, sizeof(answer), 0) != INVALID_SOCKET)
			{
				MessageBoxA(0, answer, 0, 0);
			}
		}
		WSACleanup();
	}
	ExitProcess(0);
}

BOOL WsaInit()
{
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(1, 1), &wsaData) == 0) return true;
	return false;
}

#define SOCKS_VER4		4

SOCKET ConnectToSocks4(char* proxy_addr, WORD proxy_port, char* dest_addr, WORD dest_port)
{
	hostent *hostInfo;
	sockaddr_in Destination_Addr;
	sockaddr_in Socks_Addr;
	SOCKET m_Socket;

	if (hostInfo = gethostbyname(proxy_addr))
	{
		Socks_Addr.sin_family = AF_INET;						
		Socks_Addr.sin_port = htons(proxy_port);
		Socks_Addr.sin_addr = *((struct in_addr *)hostInfo->h_addr);
		memset(&(Socks_Addr.sin_zero), 0, 8);

		if (hostInfo = gethostbyname(dest_addr))
		{
			Destination_Addr.sin_family = AF_INET;
			Destination_Addr.sin_port = htons(dest_port);
			Destination_Addr.sin_addr = *((struct in_addr *)hostInfo->h_addr);
			memset(&(Destination_Addr.sin_zero), 0, 8);

			if ((m_Socket = socket(AF_INET, SOCK_STREAM, 0)) != SOCKET_ERROR)
			{
				int retval = connect(m_Socket, (sockaddr*)&Socks_Addr, sizeof(sockaddr));
				if (retval == NULL)
				{
					//build request
					int lPacketLen = 9;
					char *packet = (char*)LocalAlloc(LMEM_ZEROINIT, lPacketLen);
					packet[0] = SOCKS_VER4; //VERSOIN
					packet[1] = 1; //COONECT CODE
					memcpy(packet + 2, (char *)&Destination_Addr.sin_port, 2); //PORT
					memcpy(packet + 4, (char *)&Destination_Addr.sin_addr.S_un.S_addr, 4); //IP
					packet[8] = 0; //USER ID

					retval = send(m_Socket, packet, lPacketLen, 0);
					LocalFree(packet);

					if (retval != SOCKET_ERROR)
					{
						//parse answer
						char reply[8];
						memset(reply, 0, 8);
						retval = recv(m_Socket, reply, 8, 0);
						if (retval != SOCKET_ERROR)
						{
							if (reply[0] == 0) //Reply code
							{
								if (reply[1] == 0x5A) return m_Socket; //OK
							}
						}
					}
				}
				closesocket(m_Socket);
			}
		}
	}
	return 0;
}

Всё довольно просто, мы используем сокеты для коннекта, сначала подключаемся на адрес прокси, формируем пакет с адресом назначения, отправляем и проверяем ответ сервера, после чего посылаем Get-запрос и выводим ответ в MessageBox.

msbx

Вопросы?)

Добавить комментарий