Scrierea unui server simplu SOCKS4 în limbaj Assembler. Server proxy universal

Anonimatul pe internet nu este un subiect nou. Și probabil ați instalat un program precum A4Proxy, SocksChain pe computer
și altele asemenea. Personal, nu îmi place când aveți nevoie de un fel de program separat pentru a lucra cu proxy. in primul rand
este urât când există o mulțime de ferestre pe bara de activități sau pictograme din tavă, în al doilea rând, aceste programe necesită fisuri și ele
Mi-e prea lene să mă uit :) De aceea am scris cursuri pentru a sprijini serverele SOCKS5, pe care acum le pot folosi
în unele dintre programele dvs. Și acum vreau să spun tuturor cum să o facă.

De ce servere și ce protocoale putem accesa printr-un proxy depinde
tipul acestui proxy, adică protocolul prin care îl accesăm. Există mai multe tipuri de proxy:
Proxy HTTP, SOCKS4, SOCKS5, SSL CONNECT etc. Proxy-urile HTTP sunt cele mai comune, sunt cele mai ușor de găsit pe Internet, dar funcționează doar cu HTTP, în plus
pot introduce adresa clientului în anteturile cererii, adică fi
nu anonim. Protocolul SOCKS este cel mai notabil prin faptul că încapsulează nu protocoale de aplicație, ci
strat de transport, adică TCP/IP și UDP/IP. Deoarece numai aceste protocoale sunt folosite pentru a funcționa pe Internet,
prin SOCKS puteți lucra cu orice server, inclusiv cu același SOCKS și,
astfel, organizați lanțuri de servere SOCKS. Din același motiv, TOATE serverele SOCKS sunt anonime - imposibil
la nivel TCP/IP și UDP/IP, transmiteți informații suplimentare fără a întrerupe activitatea celui superior
protocol.

Ne vom concentra pe protocolul SOCKS5. Descrierea sa este în
. Pentru SOCKS5 portul standard este 1080, dar, totuși, acesta
standard nimeni atentie speciala nu plateste. Fiecare conexiune SOCKS trece printr-o etapă de autentificare, dacă este necesar, apoi clientul
trimite o comandă. Comanda poate fi una din trei:
CONNECT - conexiune TCP de ieșire cu adresa specificată. Ne vom uita la utilizarea acestei comenzi
mai detaliat, deoarece este necesar cel mai des. BIND - deschide un port (serverul selectează un port și trimite adresa și portul către client) și acceptă o conexiune TCP.
Serverul poate avea nevoie să știe cine se va conecta. În acest caz, trebuie să transmiteți aceste informații. UDP ASSOCIATE - deschideți un port UDP (serverul selectează portul). Date destinate finalului
gazdă și datele de la aceasta călătoresc și prin UDP. Datele din SOCKS5 sunt transmise în formă binară, și nu sub formă de text, ca în HTTP, SMTP, POP3 etc.

Descrierea protocolului

După ce se conectează la server, clientul trimite un pachet care indică versiunea protocolului și suportat
metode de autentificare. Acest pachet are următorul format:

Versiune BYTE;
BYTE nMetode;
Metode BYTE

Versiunea trebuie să fie 5. Fiecare element de metode definește nu numai metoda de autentificare, ci și metoda de criptare a datelor,
daca este folosit. Serverul alege una dintre aceste metode. Puteți specifica orice număr de metode, dar dacă serverul nu necesită autentificare, atunci nicio metodă
altul decât 0x00 (nu utilizați nici autentificare, nici criptare) nu va fi necesar. Ca răspuns, serverul trimite un pachet cu următorul conținut:

Versiunea BYTE
Metoda BYTE

unde metoda este metoda aleasă de server sau 0xFF (niciuna dintre metodele propuse nu este acceptată). Dacă metoda este 0x00, atunci puteți trimite imediat comanda.

Pachetul de comandă are următorul format:

Versiune BYTE; // 5
BYTE Cmd ; // 1 — CONECTARE
BYTE Rezervat; // 0

BYTE adresa;
port WORD; // Octeți în ordinea rețelei, adică htons(Port);

Dacă este folosit un nume de domeniu, octetul de lungime este primul și apoi șirul fără nulul final.

Serverul trimite un răspuns:

Versiune BYTE; // 5
BYTE Rep; // 0 — Bine
BYTE Rezervat; // 0
BYTE AType; // 1 - IPv4; 3 - nume de domeniu; 4 - IPv6
BYTE adresa;
port WORD;

Aici adresa și portul sunt adresa și portul vizibile pentru gazdă. De regulă, se returnează adresa IP, nu domeniul
Nume. Această adresă poate diferi de cea la care accesăm serverul, mai ales dacă este serverul
utilizat în scopul propus, adică pentru a ieși din zona locală pe Internet. Dacă Rep nu este zero, adică o eroare, atunci închideți conexiunea, în
altfel lucrăm cu gazda. Nu folosim criptare, așa că pur și simplu trimitem și primim date ca la o conexiune normală. Dacă una dintre părți închide conexiunea la serverul de șosete, aceasta va închide imediat conexiunea cu cealaltă
latură. O conexiune de șosete încapsulează o conexiune TCP sau încearcă să stabilească una,
deci, dacă folosiți șosete pentru scanarea anonimă a porturilor, atunci aceasta
procedura poate dura o jumătate de zi.

Codificare

Deoarece șosetele încapsulează TCP, este logic să derivăm clasa de conexiune a șosetelor
clasa de socket, dar CSocket-ul MFC nu este potrivit, deoarece el are toate metodele
nu virtual. Să scriem propria noastră clasă de socket și să o numim, de exemplu, CTSocket

#include

clasa CTSocket
{
public:





virtual void Close();
virtual nesemnat lung GetHost(); // Aflați adresa dvs. De asemenea, poate fi necesar.

privat:
ciorap SOCKET;
};

Toată lumea poate scrie singur implementarea acestei clase (cine nu știe cum, RTFM MSDN), așa că nu o voi scrie
considera. Acum să scriem o clasă de conexiune pentru șosete. Acesta va suporta doar setul cel mai necesar
funcții: doar comanda CONNECT este acceptată, autentificarea și serverul SOCKS nu sunt acceptate
este specificat doar prin adresa IP, nu prin numele domeniului. Mai multe nu pot încăpea într-un articol.

Clasa CSocksSocket: CTSocket public
{
public:
virtual BOOL CreateSocket();
virtual BOOL Connect (IP lung nesemnat, port scurt nesemnat);
virtual BOOL Connect (nume LPCSTR, port scurt nesemnat);
virtual int Send(const char* str, int len);
virtual int Recv(char* buf, int max);
virtual BOOL Close();
virtual nesemnat lung GetHost();

CTSocket* pSocket;
șosete lungi nesemnate_ip;
socks_port scurt nesemnat;

privat:
tampon de carbon; // Această dimensiune este cu siguranță suficientă
l_ip lung nesemnat; // Adresă returnată de funcție
GetHost()

};

// Implementare
BOOL CSocksSocket::CreateSocket()
{
if (!pSocket->CreateSocket()) returnează FALSE;
if (!pSocket->Connect(socks_ip, socks_port)) returnează FALSE;
tampon = 5; //Ver
tampon = 1; // 1 metoda
tampon = 0; // fără autentificare
pSocket->Send(buffer, 3);
int n = pSocket->Recv(buffer, 2);
dacă (n != 2) returnează FALS;
metoda 0 nu este acceptată
returnează TRUE;
}

BOOL CSocksSocket::Connect (IP lung nesemnat, port scurt nesemnat)
{
tampon = 5; //Ver
tampon = 1; //CONECTAȚI
tampon = 0; //Rezervat
tampon = 1; // IPv4
*((nesemnat lung*)(buffer + 4)) = ip;
*((scurt fără semn*)(buffer + 8)) = port;
pSocket->Send(buffer, 10);
int n = pSocket->Recv(buffer, 10);
dacă (n != 10) returnează FALS;
dacă (buffer != 0) returnează FALSE; //
Nu se poate conecta

returnează TRUE;
}

BOOL CSocksSocket::Connect(nume LPCSTR, port scurt nesemnat)
{
tampon = 5;
tampon = 1;
tampon = 0;
tampon = 3; // Numele domeniului
int m = strlen(nume);
tampon = m; //
Lungime octet
memcpy(buffer+5, nume, m); //
Copierea unui șir fără un nul final
*((scurt fără semn*)(buffer + 5 + m)) = port;
pSocket->Send(buffer, m + 7);
int n = pSocket->Recv(buffer, 10);
dacă (n != 10) returnează FALS;
dacă (buffer != 0) returnează FALSE;
dacă (buffer != 1) returnează FALSE; //
Vom cere ca ei să ne spună IP-ul, și nu altceva.
l_ip = *((nesemnat lung*)(buffer + 4));
returnează 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();
}

CSocksSocket lung nesemnat::GetHost()
{
return l_ip;
}

//Ei bine, acum programul de testare
void main()
{
WSADATA wsadata;
CTSocket tsock;
CSocksSocket sock(&tsock);

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

ssock.socks_ip = inet_addr("10.10.10.10"); // Intra aici adresa solicitată
sock.socks_port = 1080; //
Intră aici în portul

if (!sock.CreateSocket()) return; // Nu se poate conecta la șosete
// sau autentificare necesară
dacă (!ssock.Connect("www.mail.ru", htons(80))) returnează; //
www.mail.ru
// este inaccesibil
LPSTR q = "HEAD / HTTP/1.1\xD\xAHost: www.mail.ru:80\xD\xAUser-Agent: xakep\xD\xA\xD\xA";
sock.Send(q, strlen(q));

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

O zi bună, dragi prieteni, cunoștințe, cititori, admiratori și alte persoane. În acest articol, după cum înțelegeți din titlu, vom vorbi despre ce este ȘOSETE server și de ce este nevoie de el.

Desigur, aceste informații vor fi mai utile persoanelor interesate decât utilizatorului obișnuit, dar, în principiu, cunoașterea unor astfel de lucruri îi poate fi de folos și lui... pentru că cine știe cum va decurge viața.

Descrierea generală și detaliată a SOCKS

Acesta este un protocol care permite aplicațiilor de rețea să comunice printr-un firewall care blochează conexiune directa. În acest caz, se folosește un server intermediar special, la care accesul este permis ambilor clienți.

Transmite datele primite de la primul client la al doilea și înapoi. Spre deosebire de, prin ȘOSETE un proxy poate permite orice trafic (să zicem, ). in afara de asta ȘOSETE transmite date „curate” fără a adăuga nimic în plus de la sine, deci este mai dificil de detectat.

În viața de zi cu zi ȘOSETE Proxy-ul este cel mai adesea folosit pentru a accesa site-uri care sunt interzise. În această situație, se dovedește că accesați serverul și nu o adresă de internet interzisă, deci conexiunea nu este blocată.

Al doilea motiv pentru care poți folosi aceste servere este pentru anonimat și anume ascunderea. În acest caz, nu tu ești cel care stabilește conexiunea la serverul de internet la distanță, ci ȘOSETE proxy, astfel încât serverul final este puțin probabil să primească informații despre dvs. și computerul dvs.

Postfaţă

Asta este. Sper că acum înțelegeți cel puțin aproximativ ce este în general și de ce poate fi necesar în general.

Despre cum să lucrezi în mod specific cu ȘOSETEși alți proxy pentru a-și ascunde IP va fi scris într-un articol separat, cu excepția cazului în care, desigur, cineva are nevoie și este interesat de el.

Ca întotdeauna, dacă aveți întrebări, gânduri, completări etc., atunci bineveniți să comentați acest material.

Articolul este dedicat protocolului SOCKS5 - structura sa internă, aplicație practică, precum și servere și clienți SOCKS disponibili pentru platforma Unix

[Valentin Sinitsyn (val AT linuxcenter DOT ru)]

„Îmi pare rău, Pooh”, a spus SAVA. - Tigger a mestecat toate firele de pe serverul de mail, iar corespondența nu a mai ajuns de mult...
„Fire”, gândi Pooh supărat. - Tricotează șosete din aceste fire.

Andrey Shcherbakov „9600 baud și atât, asta este, asta este...”

În acest articol vom vorbi despre protocolul SOCKS[ notă de subsol: „socks” - engleză. „șosete”, „ciorapi”]. Cu ajutorul lui poți rezolva cel mai mult sarcini diferite: organizați accesul securizat la serviciile situate în spate firewall(firewall), ascundeți adresa IP adevărată atunci când lucrați cu resurse de rețea neprietenoase sau implementați un server proxy universal care acceptă orice protocoale la nivel de aplicație (HTTP, FTP, POP3/SMTP, ICQ etc.). Din păcate, în ciuda simplității și bogăției SOCKS, mulți administratori de sistem nu sunt foarte familiarizați cu acesta și nu au idee cum poate fi util. Aș dori să sper că, după citirea acestui material, protocolul nemeritat uitat își va lua locul cuvenit în arsenalul lor. Să facem o rezervare imediat: toate prezentările ulterioare se vor referi la a cincea versiune de SOCKS, SOCKS5. Versiunea anterioară, a patra (SOCKS4) este încă în circulație pe Internet, cu toate acestea, capabilitățile sale sunt mai limitate.

Apropo, numele protocolului nu are nimic de-a face cu articolele de ciorapi menționate în epigraf și este o simplă abreviere pentru „SOCK-et-S” - „sockets” sau, într-o traducere mai familiară a unui specialist în computer. ureche, „prize”. Termenul a fost propus de creatori ca opțiune de lucru și s-a blocat. După cum știți, socket-urile sunt baza oricărui API care implementează comunicarea în rețea - Unix, Winsock etc. Pentru a trimite date prin rețea, o aplicație trebuie pur și simplu să le scrie într-un socket, similar cu ceea ce se face atunci când stochează informații într-un fișier local. În ambele cazuri, programul nu trebuie să-și facă griji cu privire la ceea ce se întâmplă „în spatele scenei” - adăugarea datelor utilizatorului cu informații despre serviciu, împărțirea lor în segmente cu încapsularea lor ulterioară în datagrame și trimiterea fizică este efectuată de alte părți ale sistemul de operare - stiva TCP / IP și driverele de dispozitiv despre care aplicația nu știe nimic. Această „diviziune a muncii” vă permite să modificați procedura de livrare a mesajelor după cum doriți, cu condiția ca interfața de programare a aplicației să rămână constantă. Această caracteristică este cea care stă la baza ideologiei SOCKS. Sarcina principală a acestui protocol este de a introduce în procesul „normal” de schimb de date un anumit intermediar numit server SOCKS sau proxy SOCKS. Când clientul (aplicație activată pentru SOCKS: browser web Mozilla, Client ICQ Miranda IM și alții, vezi mai jos) dorește să trimită unele informații prin rețea, stabilește o conexiune nu cu destinatarul real, ci cu un server SOCKS, care, la rândul său, înaintează datele către destinație, ci în nume propriu. . Din punctul de vedere al unui server „real” (de exemplu, un site Web pe care un utilizator dorește să-l vizualizeze în Mozilla Firefox), un proxy SOCKS este un client foarte obișnuit. Astfel, identitatea (adresa IP) a clientului adevărat este ascunsă de serverul care îl servește. Această circumstanță foarte convenabilă este plină de pericol potențial (vă puteți ascunde Tu, dar pot de la tine), prin urmare, serverele SOCKS din viața reală au dezvoltat scheme de control al accesului (interzicând conexiunile de intrare și de ieșire la o anumită listă de adrese) și acceptă autorizarea utilizatorului folosind o parolă (vezi mai jos).

Rețineți că, deoarece SOCKS funcționează la un nivel mai scăzut decât nivelul aplicației (și anume, transport) al modelului OSI, suportul acestuia nu va necesita nicio modificare a logicii clientului, cu atât mai puțin a serverului. Într-adevăr, tot ce este nevoie este să modifici implementarea funcțiilor responsabile cu crearea unei conexiuni la rețea și trimiterea datelor: connect(), bind(), send(), etc. În practică, acest lucru se realizează de obicei prin interceptarea apelurilor de sistem și apoi înlocuirea lor cu omologii definiți de utilizator activați de SOCKS. Nicio modificare a codului sursă aplicații client, cu atât mai puțin accesul la textele sursă, de regulă, nu este necesar. Această procedură puternică este cunoscută sub numele de „soxificare” și va fi discutată în detaliu mai jos.

Acum că avem o înțelegere de bază a SOCKS, putem trece la o privire mai detaliată. a acestui protocol.

Specificația SOCKS5

Protocolul SOCKS5 este descris în detaliu în RFC1928. Spre deosebire de standardele monstruoase precum HTTP 1.1, specificația SOCKS se încadrează în 9 pagini și poate fi analizată cu ușurință de oricine. Informațiile oferite aici sunt un scurt rezumat al acestora și au scopul de a vă ajuta în această problemă simplă.

După cum sa menționat mai devreme, SOCKS5 este un protocol de nivel de transport. „Vecinii” săi - TCP și UDP sunt utilizați direct pentru a transmite date care provin din stratul de aplicație (de la aplicații personalizate), ceea ce înseamnă că proxy-ul SOCKS trebuie să poată funcționa corect cu fiecare dintre ele. De asemenea, rețineți că protocolul ICMP utilizat utilitare pingși traceroute, este situat sub nivelul de transport și, prin urmare, din păcate, nu poate fi soxificat.[ Notă de subsol: există extensii non-standard la protocolul SOCKS care vă permit să lucrați cu ICMP, cu toate acestea, acestea nu vor fi discutate în acest articol].

Înainte de a trimite orice date, clientul trebuie să treacă printr-o procedură de autorizare pe serverul SOCKS. Pentru a face acest lucru, deschide o conexiune TCP la portul 1080 (valoarea implicită) al serverului SOCKS și trimite peste acesta un mesaj care conține numerele de cod ale metodelor de autentificare pe care le acceptă. Serverul SOCKS selectează una dintre metode la discreția sa și raportează numărul acesteia clientului. O listă a unora dintre valorile posibile este dată în Tabelul 1. După cum puteți vedea cu ușurință, autentificarea poate fi absentă (în practică, aceasta înseamnă cel mai probabil că serverul SOCKS distinge clienții după adresele lor IP) sau pe baza unui nume de utilizator si parola. În acest din urmă caz, sunt posibile un număr mare de opțiuni diferite, de la banala „Autentificare nume de utilizator/parolă” (RFC 1929), care prevede transmiterea unei parole în text clar, până la mult mai sigură CHAP (parolă criptată, date clare) și GSSAPI (RFC 1961), care pot fi utilizate pentru protecția completă a traficului criptografic. După autorizarea cu succes, clientul este capabil să trimită cereri (comenzi), să stabilească conexiuni de ieșire și chiar să le primească pe cele primite.

Stabilirea unei conexiuni TCP de ieșire

Pentru a stabili o conexiune TCP de ieșire, clientul trimite o solicitare „CONNECT” către serverul SOCKS, care specifică adresa și portul de livrare. Pentru a identifica gazda destinatarului, pot fi utilizate atât adrese IP (sunt acceptate IPv4/IPv6) cât și nume de domenii complet calificate. În acest din urmă caz, serverul SOCKS se ocupă de rezolvarea acestora, astfel că rețeaua în care activează clientul se poate, în principiu, să se facă fără un server DNS. În mesajul de răspuns, serverul SOCKS raportează un cod de eroare (ca de obicei, 0 indică faptul că operația a avut succes), precum și adresa IP (BND.ADDR) și portul TCP (BND.PORT) care vor fi folosite efectiv pentru comunica cu nodul solicitat. Deoarece serverele SOCKS au de obicei mai mult de o interfață de rețea, adresa IP dată poate diferi de cel cu care a fost stabilită legătura de control. Clientul deschide apoi o nouă sesiune TCP cu BND.ADDR:BND.PORT și trimite date. Conexiunea TCP de ieșire este încheiată când sesiunea de control este închisă. Rețineți că o solicitare CONNECT poate fi respinsă de un proxy SOCKS dacă adresele sursă (client) sau destinație (server) sunt interzise [ Notă de subsol: sau nu este permis în mod explicit, în funcție de implementarea specifică și politica aleasă] pentru a fi deservite de administratorul de sistem.

Stabilirea unei conexiuni UDP de ieșire

Spre deosebire de protocolul de streaming TCP, care implică stabilirea unei sesiuni, protocolul UDP este bazat pe datagrame și, prin urmare, este ceva mai complex de utilizat. Suportul său a apărut doar în SOCKS5.

Înainte de a trimite datagrame UDP, clientul solicită serverul SOCKS Asociația UDP folosind comanda „UDP ASSOCIATE”. O asociere UDP este un fel de sesiune virtuală între un client și un server SOCKS. În cererea de ieșire, clientul specifică pretinsadresa și portul care vor acționa ca sursă a viitoarelor datagrame UDP. Dacă această informație nu este încă cunoscută când asocierea UDP este stabilită, clientul ar trebui să folosească combinația 0.0.0.0:0 (sau, de exemplu, x.x.x.x:0 dacă doar numărul portului este necunoscut). În mesajul de răspuns, serverul SOCKS specifică adresa IP (BND.ADDR) și portul UDP (BND.PORT) către care ar trebui trimise datagramele de ieșire. În acest caz, adresa și portul destinatarului lor real sunt indicate direct în corp (putem spune că are loc încapsularea UDP). E tu parametrii, împreună cu adresa și portul expeditorului, sunt utilizați pentru a decide dacă o datagramă poate fi trimisă. După cum puteți vedea cu ușurință, acest lucru creează o încărcare suplimentară pe serverul SOCKS: regulile de filtrare trebuie aplicate fiecărei datagrame UDP, în timp ce în cazul unei conexiuni TCP, legitimitatea acesteia este evaluată o dată, în momentul în care serverul SOCKS execută „ comanda CONECTARE”. Conform standardului, serverul SOCKS trebuie să se asigure că adresa IP a expeditorului de datagramă se potrivește cu adresa gazdei care a creat asocierea UDP. Asocierea UDP este distrusă concomitent cu închiderea sesiunii de control TCP în care a fost trimisă comanda UDP ASSOCIATE.

Multe dintre serverele SOCKS existente întâmpină probleme serioase dacă un firewall NAT (Network Address Translation) este plasat între ele și clientul care solicită asocierea UDP. Motivul pentru aceasta constă în modificarea adresei sursă și a portului care are loc în momentul în care datagrama UDP traversează firewall-ul. În consecință, serverul și aplicația client nebănuitoare încep să vorbească limbi diferite: adresa sursă dorită și portul specificat în comanda „UDP ASSOCIATE” nu mai corespund parametrilor actuali ai datagramelor primite de serverul SOCKS. Ca urmare, acestea sunt aruncate ca neaparținând asociației UDP. Problema ar putea fi rezolvată prin specificarea 0.0.0.0:0 ca sursă intenționată (vezi mai sus), care ar trebui interpretată de serverul SOCKS ca „orice datagramă UDP care provine de la aceeași adresă ca și comanda de creare a asocierii”. Din păcate, majoritatea serverelor SOCKS interpretează standardul mai restrâns și nu vă permit să setați simultan atât adresa dorită, cât și portul expeditorului la zero. Dintre implementările testate de autor, „trucul cu redirecționarea UDP prin NAT” descris aici permite să se facă doar una - Dante.

Primirea conexiunilor de intrare

Această caracteristică destul de originală poate fi utilă în cazurile în care clientul și serverul „real” din schema descrisă mai sus sunt schimbate, ceea ce se poate întâmpla, de exemplu, în protocoale precum FTP. În scopul unei discuții ulterioare, vom presupune că un canal de comunicare „direct” a fost deja stabilit între „client” (partea care urmează să accepte conexiunea de intrare) și „server” (partea care inițiază conexiunea de intrare) folosind comanda „CONECTARE”. Pentru a deschide un canal „invers”, „clientul” trebuie să trimită comanda „BIND” către serverul SOCKS, specificând în parametrii acestuia adresa IP și portul pe care îl va folosi pentru a primi conexiunea de intrare. Ca răspuns, serverul SOCKS raportează adresa IP și portul alocat acestuia pentru a menține canalul „invers”. Se așteaptă ca „clientul” să transmită acești parametri „server” folosind facilitățile oferite de protocoalele de nivel de aplicație (de exemplu, comanda FTP „PORT”). După ce serverul SOCKS acceptă (sau respinge) conexiunea de intrare, re-notifică „clientul”, spunându-i adresa IP și portul folosit de „server”. Rețineți că conexiunile de intrare pot fi acceptate doar de o aplicație ai cărei dezvoltatori s-au ocupat de suportul SOCKS în etapa de proiectare. În caz contrar (dacă aplicația funcționează cu serverul SOCKS printr-un program sockets), nu va putea furniza informații corecte despre adresa socket-ului care așteaptă „feedback” (adică va genera comanda incorectă „PORT” în exemplul FTP discutat mai sus).

ȘOSETE „Lanțuri”.

Haide, treci la treabă. Șase routere închiriate „la un moment dat” prin care circulă semnalul. Și toate sunt destul de rezistente la hacking.

Serghei Lukyanenko „Labirintul reflecțiilor”

Arhitectura protocolului SOCKS5 facilitează combinarea serverelor SOCKS în cascade, sau așa cum sunt numite și „lanțuri”. Este de remarcat faptul că toate acțiunile necesare pentru aceasta pot fi efectuate pe partea clientului. Singura cerință pentru „legăturile” lanțului este ca acestea să „încredă” unul în celălalt (adică să permită stabilirea conexiunilor de intrare și de ieșire). Dacă serverele SOCKS care formează cascada nu sunt anonime (adică folosesc Username/Parolă, CHAP sau scheme de autentificare similare), este de asemenea necesar ca utilizatorul să poată finaliza cu succes procedura de autorizare pe fiecare dintre ele.

Să presupunem că avem un set de N servere SOCKS numite socks1, socks2, ..., socksN, care satisfac toate cerințele de mai sus. Apoi, pentru a crea o cascadă, clientul poate face următoarele:

    Când conexiune TCP de ieșire: clientul se conectează la socks1, parcurge procedura de autorizare (dacă este necesar) și trimite comanda „CONNECT”, specificând socks2 ca adresă de livrare. Prin executarea acestei solicitări, socks1 va crea o nouă conexiune cu socks2 și va transfera în mod regulat toate informațiile care trec prin ea către client, în timp ce socks2 nici măcar nu va ghici cu cine comunică de fapt. Procedura se repetă apoi până când se stabilește o legătură între șosete (N-1) și șoseteN. Ultimul server din cascadă se conectează direct la nodul care interesează clientul. Transferul de date are loc ca de obicei: clientul trimite un pachet către serverul socks1, care, la rândul său, îl transmite către socks2, ... și așa mai departe până când se ajunge la nodul final.

    Când conexiune UDP de ieșire: clientul se conectează la socks1, trece prin procedura de autorizare și trimite secvenţial două comenzi: „CONNECT” (adresă de livrare - socks2) și „UDP ASSOCIATE”. Astfel, sunt create două conexiuni noi: un canal UDP virtual între client și socks1 și o sesiune TCP între socks1 și socks2. Folosind această sesiune TCP, clientul (în numele socks1) trimite comanda „UDP ASSOCIATE” la serverul socks2 (deschide un canal UDP între socks1 și socks2) și „CONNECT” la serverul socks3. Procedura continuă până când sunt stabilite canale UDP virtuale între toate serverele SOCKS din cascadă. Pentru a trimite orice date, clientul efectuează mai întâi încapsularea în N-fold a datagramei UDP, specificând secvenţial socks1, socks2, socks3, socksN şi adresa destinatarului real ca adresă de livrare, apoi o trimite către serverul socks1. Rețineți că, în practică, această opțiune în cascadă este extrem de rară. Acest lucru se datorează faptului că serverele SOCKS, cum ar fi firewall-urile NAT, pot schimba portul sursă al datagramei, ceea ce va duce la probleme descrise în detaliu în secțiunea „Stabilirea unei conexiuni UDP de ieșire”.

Folosind lanțuri de servere SOCKS care nu necesită autentificare, clientul poate crește semnificativ anonimatul lucrului pe Internet (vezi epigraf). Puteți găsi multe programe pe Internet care implementează schemele descrise aici. Acestea, de exemplu, sunt SocksChain (http://www.ufasoft.com/socks/ ) pentru Windows sau ProxyChains ( ) pentru Unix. Serverele SOCKS în cascadă este, de asemenea, o parte integrantă a unor soxifier, în special FreeCap (http://www.freecap.ru/ ).

Servere SOCKS

Acum că suntem bine familiarizați cu principiile de funcționare ale unui server SOCKS, este timpul să trecem de la teorie la practică. Există un număr mare de programe în lume care implementează protocolul SOCKS5. Acopera toate cele populare OS(Unix, Windows, ...) și metode de distribuție (freeware, shareware, open-source etc.). Aici vom lua în considerare pe scurt cele mai faimoase (sau interesante din punctul de vedere al autorului) implementări.

Să începem cu SOCKS5 Reference Implementation (http://www.socks.permeo.com/), realizată de NEC și deținută în prezent de Permeo. Versiunea actuală este numerotată 1.0r11 și este datată august 2000. După cum puteți ghici cu ușurință din nume, acest server este o implementare de referință a protocolului și, în general, nu este destinat utilizării industriale. Totuși, din motive care nu îmi sunt foarte clare, a fost inclus în porturile FreeBSD și, prin urmare, este un standard de facto pe această platformă. Produsul are suport GSSAPI și este distribuit în cod sursă, dar sub o licență proprietară. Utilizarea comercială a acestui server este interzisă.

Dante, dezvoltat de compania norvegiană Inferno Nettverk, este deosebit de popular printre suporterii Linux. Produsul se dezvoltă, deși nu foarte rapid ( ultima versiune, 1.1.15, din 31 ianuarie 2005) și este destul de potrivit pentru utilizare practică. După cum am menționat mai devreme, Dante permite asocierilor UDP să funcționeze corect chiar dacă trec printr-un firewall NAT. Programul este distribuit în cod sursă sub licența BSD. Dante include o bibliotecă pentru coxificarea transparentă a aplicațiilor Unix (vezi mai jos)

Valentin Sinitsyn (val AT linuxcenter DOT ru) - Server proxy universal

Cu ceva timp în urmă am vrut să încerc să implementez un server proxy pentru nevoile mele și unul care ar putea fi folosit în viitor și, de asemenea, ca dimensiunea lui să fie minimă. Opțiunea naturală pentru mine a fost implementarea folosind assembler. Programul s-a dovedit a fi mic, convenabil, iar pe viitor l-am folosit foarte des. Dar acum, după ce au trecut ani, aș dori să arăt cea mai simplă implementare a unui protocol, SOCKS4. Acest protocol a fost creat astfel încât clienții aflați în rețeaua locală din spatele firewall-ului să poată accesa rețea externă. În același timp, în acest caz, este posibil să controlați solicitările clienților :) Primul lucru pe care trebuie să-l faceți atunci când implementați este să citiți documentația care descrie acest protocol, deoarece dorim ca protocolul nostru să fie înțeles de programele standard, fără „subminare cu un fișier.” Deci, documentația:

Acum, înarmați cu o descriere, să începem. Sarcina unui server proxy este de a accepta o solicitare de la un client într-un anumit format, de a genera un socket și de a-l conecta la adresa solicitată de client, iar apoi de a asigura schimbul de date între două socket-uri până când acestea sunt închise de către server. sau client. Să începem implementarea.

Macro-uri și structuri de date utilizate în program

Să creăm un fișier include, includes.inc. În acest fișier le vom plasa pe cele standard, la scriere programe Windows macro-uri + structuri pentru lucrul cu SOCKS4. Aici nu voi da toate macro-urile, voi da doar descrierea și funcționalitatea necesară pentru a rezolva problema principală, tot ce veți găsi în fișierul atașat cu codurile sursă.
; SOCKS4 – Structura utilizată de client la solicitarea unei conexiuni; la serverul specificat(DSTIP)/port(DSTPORT) CONNECT_SOCK4 Struc VN Db ? CD Db? DSTPORT Dw ? DSTIP Dd? NULL Db? CONNECT_SOCK4 Se termină ; SOCKS4 - răspunsul serverului proxy despre conexiune. RESPONSE_SOCK4 Struc VN Db ? CD Db? DSTPORT Dw ? DSTIP Dd? RESPONSE_SOCK4 Se încheie

De în general, structurile CONNECT_SOCK4 și RESPONSE_SOCK4 nu sunt diferite, deoarece implementăm protocolul fără autorizare. Dar am decis să le las separat, pentru ca pe viitor să le pot schimba cu ușurință pentru îmbunătățire. În structurile în sine, în variabila VN, este indicată versiunea protocolului; în cazul nostru, ar trebui să fie întotdeauna 4; în cazul SOCKS5, această variabilă conține 5 (protocolul este practic similar). Variabila CD este folosită pentru a returna clientului rezultatul solicitării serverului proxy la adresa solicitată de client (90 - conexiune reușită / 91 - conexiune eșuată).
De fapt avem trei etape în program.
* În primul rând, inițializam socket-ul, ascultăm socket-ul pentru solicitările clienților și creăm un fir de procesare.
* A doua etapă este analiza cererii clientului, o încercare de a crea și conecta un socket la serverul solicitat de client.
* Iar cea de-a treia etapă finală este trimiterea de date între priza client și priza creată și conectată de noi la adresa solicitată.

Implementarea primei etape, inițializarea programului:

; Procedura principală este procedura de pornire pentru programul WinMain Proc LOCAL ThreadId, hServSock:DWORD LOCAL nume gazdă :BYTE LOCAL _wsa:WSADATA LOCAL _our:sockaddr_in ; Lansând biblioteca pentru lucrul cu socket-uri, folosim funcționalitatea versiunii 1.1, ; să-l cerem ca minim invoke WSAStartup, 0101h, ADDR _wsa .if eax == 0 ; Luăm adresa noastră, pregătim o structură pentru a inițializa socket-ul serverului invoke gethostname, ADDR hostname, 256 invoke gethostbyname, ADDR hostname .if eax == 0 invoke inet_addr, ADDR hostname .else mov eax, mov eax, mov eax, .endif mov _our.sin_addr, eax invoke inet_ntoa, eax mov _our.sin_family, AF_INET mov _our.sin_addr.S_un.S_addr, INADDR_ANY xor eax, eax ; Introduceți portul pe care vrem să ascultăm mesajele primite mov ax, SOCKS_PORT invoke htons, eax mov _our.sin_port, ax invoke socket, AF_INET, SOCK_STREAM, 0 .if eax != INVALID_SOCKET ; Salvați socket-ul serverului creat mov hServSock, eax ; Legăm socket-ul serverului la adresa noastră și la portul necesar invoke bind, hServSock, ADDR _our, SIZEOF sockaddr_in .if eax != SOCKET_ERROR @@: ; Inițiază socket-ul pentru a aștepta invoke listen, hServSock, SOMAXCONN .repeat ; Un client a sosit, primim un socket cu clientul primit invoke accept, hServSock, NULL, NULL .until eax != INVALID_SOCKET ; Creați un fir în care clientul curent va fi procesat xchg eax, ebx invoke CreateThread, NULL, NULL, ADDR socketThread, ebx, NULL, ADDR ThreadId ; Lăsăm să așteptăm clienții jmp @B .endif .endif invoke closesocket, hServSock .endif invoke ExitProcess, 0 WinMain Endp
Aceasta este prima noastră procedură, am încercat să comentez codul pe cât posibil ca să îl înțelegeți, dar dacă ceva încă nu este clar, vă rog să mă contactați fie pe mine, fie pe MSDN. Practic, tot codul este scris folosind sintaxa MASM și WinAPI. Rezultatul funcției de mai sus ar trebui să fie o priză funcțională pe unul dintre adrese de rețea masina ta ( adresa locala, sau o adresă externă dacă aveți un IP real) + pe baza conexiunii la client, funcția creează un fir separat folosit pentru a lucra cu clientul de intrare. Acum hai sa mergem mai departe...

Etapa a doua, analiza cererii clientului

În al doilea pas, tot ce trebuie făcut este să acceptați o structură CONNECT_SOCK4, să creați un socket, să încercați să îl conectați și să trimiteți un răspuns clientului. Implementare:

SocketThread Proc sock:DWORD LOCAL lpMem, _csock, ThreadId, dAmount:DWORD LOCAL Remote:sockaddr_in LOCAL wrFds, rdFds:fd_set LOCAL hResp:RESPONSE_SOCK4 ; Pregătirea pentru a citi datele din socket invocă FdZero, ADDR rdFds invocă FdSet, sock, ADDR rdFds invocă selectează, NULL, ADDR rdFds, NULL, NULL, NULL ; Obținem dimensiunea datelor care așteaptă să fie citite invoke ioctlsocket, sock, FIONREAD, ADDR dAmount ; Rezervăm memorie pentru date mov lpMem, @Result(LocalAlloc, LMEM_FIXED sau LMEM_ZEROINIT, dAmount) ; Citiți datele cererii de la socket invoke recv, sock, lpMem, dAmount, 0; Solicitarea a venit lea edi, hResp mov esi, lpMem ; ESI conține o cerere de utilizator. Ne ocupăm (aici) doar de versiunea SOCKS4, ; SOCKS5 poate fi, în principiu, procesat aici, dar asta va veni mai târziu... Presupunem Esi: Ptr CONNECT_SOCK4 Presupunem Edi: Ptr RESPONSE_SOCK4 .if .VN == 4 ; Implementarea protocolului SOX 4 .if .CD == 1 invoke socket, AF_INET, SOCK_STREAM, 0 .if eax != INVALID_SOCKET mov _csock, eax ; Luăm datele gazda la distanta, cu care clientul dorește să se conecteze mov Remote.sin_family, AF_INET mov ax, .DSTPORT mov Remote.sin_port, ax mov eax, .DSTIP mov Remote.sin_addr, eax mov cx, .DSTPORT mov edx, .DSTIP ; Edi conține răspunsul utilizatorului mov .VN, 0 mov .DSTPORT, cx mov .DSTIP, edx ; Încercăm să ne conectăm cu server la distanta invoke connect, _csock, ADDR Remote, SIZEOF Remote .if !eax ; Pregătim un răspuns că am conectat mov .CD, 90 ; Trimitem clientului un răspuns care conține rezultatul încercării de conectare invoke send, sock, ADDR hResp, SIZEOF RESPONSE_SOCK4, 0 ; Formăm o structură cu informații despre server și; prize client conectate; - prin server aici ma refer la priza conectata la client; cine a trimis cererea; - prin client ma refer la o priza conectata la server; a cărui date au fost solicitate de clientul MOV EBX, @Result (LocalAlloc, LMEM_Fixed sau LMEM_Zeroinit, Sizeof Thread_Data) Presupunem EBX: PTR Thread_Data Mov EAX, _CSock Mov .Server, EAX MOV EAX, Sock Mov .Client, EAX Presupun EBX: Nimic; Începem firul de procesare a socketului (citirea de la client și transmiterea către socket-ul serverului) invoke CreateThread, NULL, NULL, ADDR ClientSock, ebx, NULL, ADDR ThreadId .else ; Dacă conexiunea eșuează, închideți soclul client invoke closesocket, _csock ; Spunem că a existat o eroare de conectare mov, 91; Trimitem clientului un răspuns care conține rezultatul încercării de conectare invoke send, sock, ADDR hResp, SIZEOF RESPONSE_SOCK4, 0 .endif .endif .endif .endif Assume Edi: Nothing Assume Esi: Nothing ; Eliberarea memoriei alocate pentru cerere invoke LocalFree, lpMem ret socketThread Endp
Rezultatul acestei proceduri este un socket conectat, precum și un fir creat care implementează schimbul de date între două prize. E simplu. Trebuie doar să clarificăm că mai multe puncte de adresare sunt folosite aici în cadrul structurilor care au fost introduse în MASM pentru a ușura viața programatorului. Primul punct, macrocomanda „Asumați”.
Linia Presupune Esi: Ptr CONNECT_SOCK4 spune compilatorului că acest registru (Esi) conține adresa structurii CONNECT_SOCK4, ceea ce simplifică și mai mult accesarea variabilelor din această structură. Să presupunem că Esi: Nimic nu anulează legarea. Pentru a înțelege mai bine, ar putea fi mai ușor dacă aș enumera câteva opțiuni de adresare:
Presupunem Esi:Ptr CONNECT_SOCK4 mov al, .VN ; Plasam in AL valoarea octet din variabila VN structura mov al, .CD ; Plasați axul variabil CD mov în AL. .DSTPORT ; Plasați variabila DSTPORT în AX Assume Esi:Nothing
sau
mov al, ; Plasați valoarea octetului din variabila VN în AL mov al, ; Plasați axul variabil CD mov, ; Plasați variabila DSTPORT în AX
sau
mov al, byte ptr; Plasați variabila VN în AL mov al, byte ptr ; Plasați variabila CD în AL mov ax, word ptr ; Plasați variabila DSTPORT în AX

Cred că este evident pentru tine, așa cum este și pentru mine, că este mai rapid, mai convenabil și mai clar să folosești prima opțiune. Deși dacă este necesar să ne referim la o variabilă a structurii, a doua opțiune are dreptul de a exista. Cred că este mai bine să folosiți a treia opțiune în cazurile în care datele de la adresă nu sunt structurate. Dar, după cum știți, fiecare lup Tambov are gustul și culoarea lui. Utilizați metoda care vă este cea mai convenabilă.
Încă un punct care merită clarificat. Macro-rezultat. Această macrocomandă a fost scrisă astfel încât să puteți apela o funcție WinAPI într-o singură linie și să scrieți rezultatul execuției într-un registru sau memorie. Deci linia:
mov lpMem, @Result(LocalAlloc, LMEM_FIXED sau LMEM_ZEROINIT, dAmount)
Mai întâi faceți un apel ca acesta:
invocați LocalAlloc, LMEM_FIXED sau LMEM_ZEROINIT, dAmount
iar după executarea acestui apel, rezultatul execuției (Eax) este stocat în variabila lpMem. În acest caz particular, memoria va fi alocată, iar adresa la care se află zona alocată pentru noi va fi scrisă în variabilă.

Etapa a treia, transferul de date

Deci, cele mai dificile două etape au fost finalizate. Clientul a sosit, l-am conectat la serverul de la distanță și a venit timpul pentru cea mai simplă muncă „maimuță”. Transferați date între două prize. Să o facem rapid și ușor:
; Un flux care citește din socket-ul clientului și îl trimite la socket-ul serverului... ClientSock Proc Param:DWORD LOCAL server, sclient:DWORD LOCAL rdFds:fd_set LOCAL dAmount, lpBuf: DWORD ; În Param avem informații despre server și socket-uri client; transfer la variabilele locale mov ebx, Param Assume Ebx: Ptr THREAD_DATA mov eax, .Server mov server, eax mov eax, .Client mov sclient, eax Presupun Ebx: Nimic ; Nu uitați să eliberați memoria invocă LocalFree, Param @@: invocă FdZero, ADDR rdFds invocă FdSet, server, ADDR rdFds invocă FdSet, sclient, ADDR rdFds invocă selectează, NULL, ADDR rdFds, NULL, NULL; Verificați dacă există date de citit.if eax == SOCKET_ERROR || eax == 0 ; Fără date - exit jmp @F .endif ; Există date de la server care trebuie transmise clientului? invocă FdIsSet, server, ADDR rdFds .if eax ; Obținem dimensiunea datelor care așteaptă să fie citite invoke ioctlsocket, server, FIONREAD, ADDR dAmount ; Rezervați memorie pentru date mov lpBuf, @Result(LocalAlloc, LMEM_FIXED sau LMEM_ZEROINIT, dAmount) invoke recv, server, lpBuf, dAmount, 0 .if eax == SOCKET_ERROR || eax == 0 jmp @F .endif invoke send, sclient, lpBuf, eax, 0 invoke LocalFree, lpBuf .endif ; Există date de la client de trimis la socket-ul serverului? invocă FdIsSet, sclient, ADDR rdFds .if eax ; Obținem dimensiunea datelor care așteaptă să fie citite invoke ioctlsocket, sclient, FIONREAD, ADDR dAmount ; Rezervă memorie pentru date mov lpBuf, @Result(LocalAlloc, LMEM_FIXED sau LMEM_ZEROINIT, dAmount) invoke recv, sclient, lpBuf, dAmount, 0 .if eax == SOCKET_ERROR || eax == 0 jmp @F .endif invocă trimitere, server, lpBuf, eax, 0 invocă LocalFree, lpBuf .endif ; Să mergem la ciclu nou jmp @B @@: ; Închideți socket-urile invoke closesocket, server invoke closesocket, sclient ; Ieșiți din firul de invocare ExitThread, 0 ClientSock Endp
Inițial, această procedură inițializează variabilele interne din structura transmisă în flux pentru a le face mai ușor de utilizat. Apoi, în buclă, se face o verificare pentru a vedea dacă sunt date de citit din socketuri, apoi în două bucăți de cod (de fapt copy-paste, aici nu m-am deranjat să scot funcția și să o optimizez pentru că e mai clar ) citește de la o priză și trimite-l la al doilea.
Asta e, hai! Să compilam și să încercăm. În principiu cel mai mult o opțiune bună– FireFox. În setările de conectare, precizăm că trebuie să utilizați un server proxy SOCKS4. Indicăm adresa acestuia și portul pe care se află. După aceea, salvăm setările și ne bucurăm de internet, trecut prin proxy-ul nostru, cu o dimensiune de 3,5 kbytes))) Da, voi clarifica. Pentru compilare este necesar să aveți pachetul instalat