Сегодня мы разберём протокол 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 05 | IP-адрес (5.255.255.55 ) |
00 | пустой ID пользователя |
И получаем от сервера:
00 | null-байт |
5A | запрос предоставлен |
00 00 | 2 произвольных байта |
00 00 00 00 | 4 произвольных байта |
Дальше мы посылаем запрос к хосту яндекса, так же как бы мы делали это без-прокси, обычный 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.
Вопросы?)