Proxy сервер на Delphi і Winsock API

  1. трохи теорії
  2. Навіщо писати свій проксі-сервер
  3. використовувані технології
  4. Winsock API для Proxy
  5. Кодима
  6. Код потоку TClientThread
  7. Слухове вікно прорублене

Ми багато разів розповідали тобі про проксі-сервери: для чого вони потрібні, як вони працюють і чим корисні хакеру. Проте знати і використовувати - це одне, а ось створювати самому - зовсім інша справа. Ця творча праця корисний для душі, тіла і, звичайно ж, твого WM-гаманця.

трохи теорії

Отже, проксі-сервер - це перш за все програма, яка виступає посередником між клієнтом і сервером. Всі звикли пов'язувати поняття проксі тільки з протоколом HTTP. Насправді існують проксікі і для інших протоколів, про які я розповім трохи пізніше. Найпоширеніший вид проксіков - HTTP. При роботі через HTTP-проксі твій браузер не буде з'єднуватися з сервером, на якому розташований запитуваний сайт, він з'єднається з проксі і передасть йому запит. Отримавши від тебе всі необхідні дані, проксік сам сконнектітся з віддаленим web-сервером і відправить твій запит. Після його обробки web-сервер поверне документ проксіку, який потім відправить його тобі. Такі проксікі корисні, коли потрібна анонімність (оскільки вони бувають прозорими) або якщо твій провайдер обмежує тебе і не дозволяє відвідувати сайти, розташовані на зарубіжних серверах.

Ще одне місце, де постійно використовуються проксі-сервери, - корпоративні (домашні) локальні мережі. Для надання співробітникам компанії доступу в інет адміни встановлюють на шлюзі проксік, і вся контора ходить через нього в Мережу. Плюси такого способу очевидні: можна легко відстежувати маршрути користувачів, рахувати кількість витраченого трафіку і бути впевненим в тому, що користувачі не користуватимуться зайвим софтом, так як не кожна програма здатна працювати через HTTP-проксі.

Я вже говорив, що HTTP-проксі не є єдиним типом проксі-серверів. У природі також зустрічаються:

  • HTTPS proxy - один з найбільш універсальних типів проксі-серверів. У ньому реалізована підтримка специфікації протоколу HTTP 1.1. Особливість цієї версії протоколу - підтримка методу CONNECT, завдяки якому стає можливим працювати з HTTPS (безпечним HTTP), а також змусити працювати через проксі-сервер програми на кшталт ICQ, функціонування яких через HTTP-проксі неможливо.
  • FTP proxy - досить рідкісний вид, занесений до Червоної книги. Як і випливає з назви, ці проксі призначені для роботи з FTP-протоколом. Головна їхня особливість - можливість роботи як в пасивному, так і в активному режимах.
  • Socks proxy - найбільш просунутий тип проксіков. Такі проксі-сервери працюють з будь-яким TCP / IP-протоколом (ftp, pop3, smtp, nntp і т.д.).
  • Навіщо писати свій проксі-сервер

    Розібравшись на практиці з основами написання проксі-серверів, ти зможеш поповнити колекцію] [- тулз власного виготовлення. Наприклад, можна без праці зробити проксі абсолютно невидимим в системі. Підсунувши таку штуку сусідові, в разі якщо він не думає про security і не юзает файрвол, хакер без проблем зможе ганяти інет-трафік через його комп, насолоджуючись халявою.

    Іншим цікавим способом застосування твого шедевра може бути сніфаніе хакером паролів, які сусід вводить в своєму браузері. В цьому випадку хакеру також потрібно буде підкинути нещасному сусідові твою ТУЛЗ і переконати його запустити її. Після запуску] [- проксік автоматично сконфигурирует бродилку сусіда на роботу через самого себе. Тим самим чол буде спокійно борознити інет, а все його запити (відправка паролів і т.д.) будуть записуватися лог. Круто? Безсумнівно! Але ми-то з тобою знаємо, що всі ці маревні ідеї носять протизаконний характер, тому ми будемо писати проксі-сервер лише в освітніх цілях, навіть і не думаючи про отримання вигоди.

    використовувані технології

    При написанні серверних мережевих додатків не рекомендується використовувати компонентну модель Delphi. Компоненти не володіють тією гнучкістю, яку можна отримати, застосовуючи API. Тому сьогодні нам знову доведеться зіткнутися зі страшним WinSock API.

    Тепер давай обговоримо алгоритм роботи нашого майбутнього проксі-сервера. Оскільки ми будемо створювати серверний додаток, йому просто необхідно бути розрахованих на багато користувачів. Ти тільки уяви корпоративний проксі-сервер, яким може користуватися тільки одна людина, а решта тим часом будуть нервово курити в стороні. Отже, раз наш додаток буде розрахованих на багато користувачів, то оптимально використовувати потоки. При підключенні клієнта для нього буде створюватися окремий потік. Таким чином наш сервер зможе одночасно працювати з декількома користувачами.

    Після установки з'єднання з клієнтом - браузером користувача - перше, що нам необхідно буде зробити, - це отримати запит клієнта. Отримавши запит і витягнувши з нього адреса віддаленого сервера, треба відразу спробувати з'єднатися з ним, передати отриманий раніше запит, дочекатися відповіді і переслати отриману відповідь назад клієнту. Якщо ти уважно слухав теорію, то міг помітити, що в якості віддаленого сервера не обов'язково повинен виступати web-сервер. На його місці цілком може бути і інший проксік. Таким чином, можна створити справжню ланцюжок проксіков, що, безсумнівно, підвищить анонімність.

    Обговоримо отримання запиту від клієнта. У запиті, який формує браузер, міститься інформація, на підставі якої web-сервер може визначити, який саме web-документ ми від нього хочемо. Всі нюанси запитів ти можеш дізнатися з RFC 2068. Розглянемо приклад. Коли ти набираєш в браузері www.xakep.ru, запит має наступний вигляд (може відрізнятися, залежить від браузера):

    GET http://xakep.ru/ HTTP / 1.0 User-Agent: Opera / 9.21 (Windows NT 5.1; U; ru) Host: xakep.ru Accept: text / html, application / xml; q = 0.9, application / xhtml + xml, image / png, image / jpeg, image / gif, image / x-xbitmap, * / *; q = 0.1 Accept-Language: ru-RU, ru; q = 0.9, en; q = 0.8 Accept-Charset : iso-8859-1, utf-8, utf-16

    Як бачиш, в запиті міститься багато корисної та марної інформації, але найголовніше - це перша і третя сходинка. У першій визначено адреса, який запросив користувач, а в другій - хост. Отримавши цей запит, наш проксік повинен отримати адресу хоста, визначити його IP, з'єднатися і послати йому весь запит. Напевно у тебе виникло питання: чи можна змінити цей запит? Відповідаю. Звичайно можна! Ми легко зможемо познущатися над користувачем, і замість www.yandex.ru він буде потрапляти на www.xxx-porno.com.

    Winsock API для Proxy

    Як і личить в програмуванні, після обговорення алгоритму вирішення поставленого завдання, потрібно визначитися з інструментами, які будуть необхідні для цього. У нашому випадку головним молотком буде Delphi, а цвяхами з шурупами - WinSock API і класи TThread. Розглянемо необхідні WinSock API функції.

    function WSAStartup (wVersionRequested: word; var WSAData: TWSAData): integer; stdcall;

    Ця функція, з виклику якої потрібно починати програмування будь-якого мережевого додатки. Вона призначена для ініціалізації мережевої бібліотеки Windows. Функції необхідно заслати два параметри:

  • wVersionRequested - версія ініціалізіруемих бібліотеки. Їх всього дві: 1.1 і 2.0. Наприклад, для першої версії пишемо: makeword (1,1).
  • Покажчик на структуру WSAData. Після виконання функції в цю структуру запишеться інформація про мережевий бібліотеці.
  • При успішному виконанні функція поверне 0. Для отримання кодів помилок в WinSock API служить функція WSAGetLastError (). Їй не потрібно передавати будь-які параметри, після виклику вона повертає код останньої виникла при роботі з мережевими функціями помилки.

    function socket (af: integer; type: integer; protocol: integer): TSocket, stdcall;

    Перед тим як з'єднатися з віддаленим вузлом, потрібно створити «розетку» - socket. Якраз за його створення і відповідає однойменна функція socket. Вхідних параметрів три:

  • af - сімейство протоколів. Нам буде потрібно лише TCP, тому будемо вказувати AF_INET.
  • type - тип створюваного сокета. Може бути sock_stream (для протоколу TCP / IP) і sock_dgram (udp).
  • protocol - протокол. Для TCP потрібно вказати IPPROTO_TCP.
  • Результатом виконання буде новий сокет. Створивши сокет, можна пробувати підключатися. Для цього в бібліотеці реалізована функція Connect.

    function Connect (S: TSocket; var name: TSockAddr; namelen: integer): Integer: stdcall;

    Параметрами для функції служать:

  • s - socket, створений функцією socket.
  • name - структура SockAddr, що містить дані, необхідні для підключення (протокол, адреса віддаленого комп'ютера, порт).
  • namelen - розмір структури типу TSockAddr.
  • Успішно виконати, а значить, і встановивши з'єднання, функція поверне 0, в іншому випадку - помилку, яку можна отримати за допомогою WSAGetLastError ().

    Структура TSockAddr виглядає так:

    TSockAddrIN = sockaddr_in; SockAddr_in = record sin_family: u_short; // сімейство протоколів sin_port: u_short; // порт, з яким потрібно буде встановити з'єднання sin_addr: TInAddr; // структура, в якій записана інформація про адресу віддаленого комп'ютера sin_zero: array [0..7] of Char; // суміщення по довжині структури sockaddr_in з sockaddr і навпаки. end;

    Читання і відправка даних лише у віддаленій стороні здійснюється за допомогою функцій send і recv. Вони описані в такий спосіб:

    function send (s: TSocket, var Buf; len: integer; flags: integer): Integer; stdcall; function recv (s: TSocket, var Buf; len: integer; flags: integer): Integer; stdcall;

    Параметри для обох функцій однакові:

  • s - сокет, на який потрібно відправити (прийняти) дані.
  • buf - буфер з даними для відправки (прийому).
  • len - розмір переданих (прийнятих) даних.
  • flags - прапори, що відповідають за метод відправки (прийому).
  • Виконати, функція поверне фактична кількість відправлених / прийнятих байт.

    function bind (S: TSocket; var addr: TSockAddr; namelen: Integer): integer; stdcall;

    Призначення функції - зв'язування структури TSockAddr зі створеним сокетом. Параметрів три: сокет, структура, розмір структури.

    function listen (s: TSocket; backlog: Integer): Integer; stdcall;

    Фактичне прослуховування порту починається після виклику цієї функції. Для роботи функції потрібно всього два параметри: сокет і максимальну кількість запитів на очікування підключення.

    function CloseSocket (s: TSocket): integer; stdcall;

    Ця функція закриває сокет. Параметр всього один - сокет, який потрібно закрити.

    function Select (nfds: Integer, readfds, writefds, exceptfds: PFDSet, timeout: PTimeVal): LingInt; stdcall;

    Мета функції - перевірка готовності сокета (читання, запис термінових даних). Select дуже пригождается, коли потрібно розробляти розраховані на багато користувачів мережеві додатки подібно до нашого, де використання подієвої моделі Windows не виправдовує себе. Як параметри функція приймає:

  • nfds - параметр ігнорується і присутній лише для сумісності з моделлю сокетов Берклі.
  • readfds, writefds, exceptfds - вони визначають можливість читання, записи і факт прибуття термінових даних. Ці три параметри є покажчиками на структуру FD_SET, яка представляє собою набір гнізд.
  • TimeOut - покажчик на структуру timeval. У структурі визначено максимальний час очікування. Для установки нескінченного очікування стала помітно меншою передати в цей параметр nil.
  • procedure FD_ZERO (var FDSet: TFDSet);

    Очищення і ініціалізація набору сокетов. Перед додаванням сокетов в набір необхідно його проинициализировать за допомогою цієї функції.

    procedure FD_SET (Socket: TSocket; var FDSet: TFDSet);

    Процедура призначена для додавання сокета, переданого в першому параметрі в набір, зазначений у другому.

    function FD_ISSET (Socket: TSocket; var FDSet: TFDSet): Boolean;

    Функція дозволяє перевірити входження сокета (перший параметр) в набір (другий параметр).

    Кодима

    Ось і настала та заповітна хвилина, коли ми закінчуємо розбиратися з теорією і приступаємо до реального КОДІНГ. Запускай Delphi, створюй новий проект і додай формі вид, схожий на вигляд моєї форми. Ми не будемо нічого приховувати від користувача, оскільки, якщо ти пам'ятаєш, ми пишемо програму в освітніх цілях. Ти там далі сам розберешся. На формі у мене три кнопки:

  • «Запустити» - запускає проксік на порту 8080.
  • Configure IE - для автоматичного конфігурування браузера IE.
  • Configure Opera - те ж саме конфігурація, тільки для Opera.
  • В іншій частині форми у мене розташовується ListView з трьома колонками. У них ми будемо відображати IP клієнтів і адреси хостів, до яких вони звернулися. За подією OnClick для кнопки «Запустити» напиши наступний код:

    _listenThread: = TListenThread.Create (false);

    Цією однієї-єдиної рядком коду ми створюємо новий потік типу TListenThread. Потоки можна створювати припиненими. Саме тому в якості параметра методу Create я передаю значення false, що вимагає негайного запуску.

    Потік TListenThread підготує сокет для прослуховування і чекатиме підключень на порт 8080. Код створення наведено в урізанні «Потік TListenThread».

    var _listenSocket, _clientSocket: TSocket; _listenAddr, _clientAddr: sockaddr_in; _clientThread: TClientThread; _size: integer; begin _listenSocket: = socket (AF_INET, SOCK_STREAM, 0); if (_listenSocket = INVALID_SOCKET) then begin ShowMessage ( 'Помилка створення сокета!'); Exit; end; _listenAddr.sin_family: = AF_INET; _listenAddr.sin_port: = htons (8080); _listenAddr.sin_addr.S_addr: = htonl (INADDR_ANY); if (Bind (_listenSocket, _listenAddr, sizeof (_listenAddr))) = SOCKET_ERROR then begin ShowMessage ( 'Помилка зв'язування сокета з адресою!'); Exit; end; if (Listen (_listenSocket, 4)) = SOCKET_ERROR then begin ShowMessage ( 'Не можу почати прослуховування!'); Exit; end; while true do begin _size: = sizeof (_clientAddr); _clientSocket: = accept (_listenSocket, @_clientAddr, @_size); if (_clientSocket = INVALID_SOCKET) then Continue; _clientThread: = TClientThread.Create (true); _clientThread._Client: = _ClientSocket; _clientThread._ip: = inet_ntoa (_clientAddr.sin_addr); _clientThread.Resume; end;

    Давай докладніше розглянемо вміст наведеної вище врізки. Процедура Execute (), певна у об'єкта TlistenThread, є основною для потоків. Після запуску потоку вона виконується найпершої, а раз так, то саме в ній потрібно розташувати код, який відповідає за початок прослуховування певного порту.

    Щоб почати слухати порт, потрібно створити сокет за допомогою однойменної функції socket (). Параметри, необхідні для роботи функції, визначаються виходячи з того, який протокол ми будемо використовувати. HTTP-проксік повинен задіяти TCP / IP-протокол, що забезпечує надійну передачу даних. Тому в другому параметрі я вказую SOCK_STREAM.

    Створивши сокет, потрібно переконатися, що після виконання функції Socket не відбулася помилка. Для перевірки досить порівняти змінну сокета зі значенням константи INVALID_SOCKET. Якщо вони виявляться рівними, то сталася помилка і подальше виконання програми безглуздо. Припустимо, що сокет успішно створився, а значить, наступним кроком буде заповнення структури sockaddr_in, що містить необхідні дані для початку прослуховування.

    Детальний опис всіх властивостей структури я вже наводив, тому зараз не буду загострювати на цьому увагу. Заповнивши всі властивості структури, її потрібно зв'язати з нашим сокетом за допомогою функції BIND. Якщо функція BIND виконалася без помилок, то треба викликати функцію для початку прослуховування - Listen. Після її виконання запускається нескінченний цикл, в якому викликається функція accept (). Успішне її виконання буде означати, що до нас приєднався клієнт, і для роботи з ним необхідно створити новий потік. У потоці TClientThread відбуватиметься обмін даними між клієнтом і нашим проксіком і, відповідно, між проксіком і віддаленим сервером. Основний код потоку TClientThread наведено в урізанні, а повну версію ти завжди можеш подивитися на нашому диску.

    Код потоку TClientThread

    var _buff: array [0..1024] of char; _port: integer; _request: string; _srvAddr: sockaddr_in; _srvSocket: TSocket; _mode, _size: Integer; _fdset: TFDSET; begin Recv (_client, _buff, 1024, 0); _request: = string (_buff); if _request = '' then begin CloseSocket (_client); exit; end; _host: = Copy (_request, Pos ( 'Host:', _request), 255); Delete (_host, Pos (# 13, _host), 255); Delete (_host, 1, 6); _port: = StrToIntDef (Copy (_host, Pos ( ':', _host) +1, 255), 80); Delete (_host, Pos ( ':', _host), 255); if (_host = '') then begin SendStr (_client, '<h1> Error 400: Invalid header </ h2>'); CloseSocket (_client); exit; end; Synchronize (addToLog); _srvSocket: = socket (AF_INET, SOCK_STREAM, 0); _srvAddr.sin_addr.s_addr: = htonl (INADDR_ANY); _srvAddr.sin_family: = AF_INET; _srvAddr.sin_port: = htons (_port); _srvAddr.sin_addr: = LookupName (_host); if connect (_srvSocket, _srvAddr, sizeof (_srvAddr)) = SOCKET_ERROR then begin SendStr (_Client, '<h1> Error 404: NOT FOUND </ h1>'); exit; end; _mode: = 1; setsockopt (_srvSocket, IPPROTO_TCP, TCP_NODELAY, @_mode, sizeof (integer)); send (_srvSocket, _buff, strlen (_buff), 0); while true do begin FD_ZERO (_fdset); FD_SET (_client, _fdset); FD_SET (_srvSocket, _fdset); if (select (0, @_fdset, nil, nil, nil) <0) then exit; if (FD_ISSET (_client, _fdset)) then begin _size: = recv (_Client, _buff, sizeof (_buff), 0); if _size = -1 then break; send (_srvSocket, _buff, _size, 0); continue; end; if (FD_ISSET (_srvSocket, _fdset)) then begin _size: = recv (_srvSocket, _buff, sizeof (_buff), 0); if _size = 0 then exit; Send (_client, _buff, _size, 0); continue; end; end; CloseSocket (_client); CloseSocket (_srvSocket);

    Цей код вийшов трохи більше за розміром, але складного в ньому нічого немає, в чому ти зараз переконаєшся. Для того щоб розібратися в цьому лістингу, доведеться згадати алгоритм роботи нашої програми, який ми обговорювали в самому початку статті. Під час активного з'єднання з клієнтом, потрібно відразу отримати від нього текст запиту, в якому визначено адреса запитуваного документа. Читання даних з сокета відбувається функцією Recv (), опис якої я наводив.

    Отримавши текст запиту, потрібно висмикнути з нього значення атрибута «хост». За цим значенням ми зможемо отримати адресу віддаленого сервера, якому і будемо відправляти запит користувача. Якщо із запиту вдалося виділити адресу хоста, потрібно починати готувати сокет для установки з'єднання з web-сервером, в іншому випадку відправити користувачеві повідомлення типу «Помилка в запиті». Для установки з'єднання потрібно заповнити вже знайому нам структуру типу sockaddr_in і виконати функцію Connect ().

    Як тільки з'єднання буде встановлено, потрібно перевести сокет в асинхронний режим. Зміна режиму відбувається за допомогою функції setsockopt (). Переклад в асинхронний режим необхідний, оскільки в такому випадку нехило підвищиться продуктивність нашого застосування. Це стане можливим через мінімізацію затримок перед пересиланням даних між нами, web-сервером і клієнтом. Отримавши від сервера порцію даних, ми не будемо чекати інших, а будемо відразу відправляти її клієнту.

    Отже, перевівши сокет в асинхронний режим, можна сміливо відправляти серверу запит, раніше отриманий від клієнта і запускати нескінченний цикл, в якому буде реалізований обмін даними. Перед тим як читати дані, потрібно додати сокети в набір для очікування. Як ти пам'ятаєш, після ми зможемо перевіряти готовність сокета за допомогою функції Select ().

    Ну а далі все просто. Залішається только сделать Перевірки сокетов. Если запит прийшов від клієнта, то відкрівається его web-серверу; если від сервера, то, навпаки, відправляємо его клієнту. Для перевірки я запустив створений проксік у себе на компі, сконфігурував Opera і спробував зайти на один із сайтів локальної мережі, користувачем якої я з'явлюся. Після відправлення запиту моя опера шустренько почала приймати дані від проксі-сервера. Тим часом ListView став заповнюватися моїм IP і адресою хоста, до якого я посилав запит.

    Слухове вікно прорублене

    Сподіваюся, сьогоднішній приклад вийшов досить корисним як для програміста, так і для хакера. В черговий раз ти переконався, що Delphi - це не тільки бази даних і звіти, але і мову, за допомогою якого можна вирішувати як прикладні, так і хакерські завдання. Мені залишається тільки побажати тобі успішного застосування отриманих знань в своїх майбутніх проектах.

    Исходник прикладу Proxy сервер на Delphi

    Стаття опублікована Журналі "Хакер" (http://xakep.ru). Січень 2008 р

    Посилання на агентство опубліковану статтю сайту видання: http://goo.gl/juyZFN

    Посилання на агентство журнал: http://goo.gl/q7uKzg

    Круто?
    Напевно у тебе виникло питання: чи можна змінити цей запит?