SOCKS, ou comment hacker derrière une montagne... -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- Version 0.9 -*- Avril 2001 I. Introduction II. Trouver une belle montagne... III. ...et se cacher derrière IV. En savoir plus sur SOCKS V. Conclusion VI. Annexe : bouncer.c (version 1.1) I. Introduction ---------------- Vous aimez l'exploration des réseaux et systèmes informatiques mais la chose qui vous embête est que vous laissez des traces, à savoir votre adresse IP ? Vous souhaiteriez donc changer cette adresse IP, afin que cela soit plus difficile de remonter jusqu'à vous ? Vous connaissez sûrement la méthode qui consiste à passer par un ou plusieurs proxies. Ce document traite des proxies de type SOCKS. Qu'est-ce que SOCKS ? C'est un protocole conçu pour mettre en place un serveur proxy entre deux réseaux : ._______________. .-->--| |-->--. Réseau A | | Serveur SOCKS | | Réseau B `--<--|_______________|--<--' Pour accéder à une machine du réseau B, une machine du réseau A effectue une requête auprès du serveur SOCKS. Si cette machine A est autorisée à se connecter, le proxy va relayer les échanges entre les deux machines A et B. Comme vous l'avez sûrement deviné, un serveur SOCKS sert souvent de firewall entre un LAN et Internet : .__________________. .-->--| |-->--. LAN | | Firewall (SOCKS) | | Internet `--<--|__________________|--<--' (SOCKS est très souple et permet de définir des architectures bien plus complexes). La grande qualité du protocole SOCKS est qu'il se situe entre la couche Application et la couche Transport. Par conséquent, toutes les applications TCP et UDP passeront par le serveur SOCKS, contrairement à d'autres types de proxies qui ne fonctionnent que pour certaines applications. Attention, les applications ICMP (comme ping et traceroute) ne seront pas relayées par le serveur, car ICMP est situé dans la couche Réseau... Cependant, il existe des techniques pour utiliser ces outils à travers un serveur SOCKS. Une petite précision à propos de SOCKS : la version 5 du protocole supporte UDP et IPv6, et inclus un système d'authentification. Alors, comment masquer mon adresse IP ? En passant par un ou plusieurs proxies SOCKS mal configurés (qui vous laissent les traverser sans authentification ;o) : vous --> proxy 1 --> proxy 2 --> ... --> proxy x --> serveur Le serveur "voit" l'adresse IP du proxy x, qui lui-même "voit" l'adresse IP du proxy x - 1, etc. ATTENTION : n'oubliez surtout pas qu'il est toujours possible de remonter jusqu'à vous... II. Trouver une belle montagne... ---------------------------------- Trouver des proxies SOCKS n'est pas très difficile : il suffit d'aller sur CyberArmy pour y trouver une liste mise à jour très souvent. Oui, certes. Mais : - je ne pense pas que vous soyez le seul à connaître CyberArmy. Ces proxies sont utilisés par beaucoup de monde (y compris par des enfants qui font des bêtises sur IRC et ailleurs), et bien souvent, leur durée de vie est très courte. - ces proxies sont utilisés par beaucoup de monde, comme je viens de le dire, et donc ce n'est pas la plus discrète des solutions (à moins qu'au contraire vous ne préfériez utiliser des proxies très utilisés, pour être caché par tout le monde : mouais... --> dans ce cas, je pense que la sécurité à travers l'obscurité est une meilleure solution, mais je me trompe peut-être). La solution est donc de trouver soi-même ses propres proxies. Pour m'amuser, et pour répondre à mes besoins, j'ai créé un petit scanner de proxies SOCKS. Vous le trouverez sur notre site (http://hell.unixlover.com). Cela vous intéressera peut-être (je vous préviens tout de suite que ce n'est pas un superbe programme :o). Ainsi, vous serez le seul à connaître leur existence... en théorie, et sans oublier leur propriétaire bien sûr ! Quelques précautions à prendre : - n'utilisez pas de proxies situés dans votre pays --> le but est ainsi de rendre la tâche plus difficile à quelqu'un qui souhaitera remonter jusqu'à vous (ainsi, un proxy situé en Alaska est une bonne solution, mais pensez aussi au fait que le proxy doit avoir un bon ping, surtout si vous en utilisez plusieurs...). - évitez d'utiliser des proxies "officiels", qui appartiennent à une société : je vous conseille plutôt de scanner les machines d'utilisateurs du câble ou de l'ADSL (mais *pensez* à ces utilisateurs : n'utilisez pas leur proxy pour faire n'importe quoi...). En général la connexion est de bonne qualité :o). Et cela posera beaucoup plus de problèmes à celui désirant vous identifier. Ces proxies ont une durée de vie variable, n'hésitez pas à re-scanner de temps en temps le réseau... Note : vous trouverez parfois ce genre de proxies sur CyberArmy (ou ailleurs). Regardez à quel réseau ils appartiennent et scannez ce réseau : vous aurez parfois de très bonnes surprises (il m'est arrivé de trouver une bonne douzaine de proxies sur 254 machines, alors qu'une seule était indiquée sur le site web qui proposait une liste de proxies)... Si vous n'utilisez plus vos proxies, donnez-les à des amis, ou à tout le monde sur Internet (ne faites pas de cadeaux "empoisonnés" : ne donnez pas de proxies qui ne fonctionnent pas, bien évidemment, et si vous savez que le proxy a un problème, par exemple qu'il appartient à la NSA ;o), indiquez-le !!!). Voilà. A vous d'avoir de l'imagination pour tout le reste... Et réflechissez en choisissant vos proxies (Où est-il situé ? A qui appartient-il ? Est-il rapide ? etc.). N'ACCORDEZ PAS UNE CONFIANCE AVEUGLE AUX PROXIES !!! dnsquery, nslookup, whois, ping, traceroute, sockscanner, etc. sont vos amis, ne les oubliez pas ! III. ...et se cacher derrière ------------------------------ Certains programmes (clients IRC, clients web, etc.) supportent SOCKS, et vous permettent donc de spécifier un proxy à traverser. Le problème est que rien n'est centralisé : si vous désirez changer de proxy (ce qui arrive en principe très souvent), vous devez reconfigurer un à un tous vos programmes. Le mieux est d'avoir un seul fichier de configuration pour tous les programmes du système. De plus, peu de programmes supportent SOCKS. L'implémentation de référence de SOCKS, développée par Nec (et basée sur la version originale créée par David Koblas), est disponible sur le site www.socks.nec.com (notez que ce n'est pas exactement un logiciel libre; mais, étant donné que les restrictions imposées par la licence ne concernent qu'une utilisation commerciale du package, cela ne nous gênera pas). Ce package contient quelques programmes "socksifiés" (comme ftp, telnet, finger, etc.) : en effet, il est nécessaire de modifier et de recompiler tous les programmes que l'on souhaite utiliser à travers un proxy SOCKS ! Cependant, il existe une bien meilleure méthode, qui fonctionne avec tous les programmes du système et qui ne nécessite aucune modification/recompilation. Cette méthode consiste à linker dynamiquement aux programmes une bibliothèque partagée qui se place avant la bibliothèque C standard (la libc), et qui intercepte les appels-système comme connect(), bind(), etc. afin de "socksifier" les programmes lorsqu'ils sont exécutés. Cette méthode est très intéressante, comment l'utiliser ? Il faut commencer par compiler la bibliothèque, d'une façon très classique : ./configure make lib shlib # shlib : pour utiliser le script runsocks (voir plus bas) (vous pouvez bien sûr indiquer des options au script configure, si cela vous intéresse lisez les fichiers README et INSTALL, mais la configuration par défaut conviendra à la plupart d'entre vous). Si tout cela s'est déroulé sans erreurs, un fichier nommé libsocks5.a est apparu dans le répertoire lib. Ce fichier est une archive que nous allons tout de suite transformer en bibliothèque partagée : gcc -shared -lsocks5 mv a.out /usr/lib/libsocks5.so Maintenant, nous devons créer un fichier de configuration nommé libsocks5.conf dans le répertoire /etc. Chaque ligne de ce fichier doit respecter cette syntaxe : proxy cmd dest-host dest-port [userlist [proxylist]] proxy : indique le type de proxy --> socks4, socks5 ou noproxy cmd : le signe "-" indique que vous pouvez utiliser toutes les commandes dest-host : "-" indique que vous pouvez accéder à tous les hôtes derrière le proxy dest-port : "-" indique que vous pouvez accéder à tous les ports derrière le proxy userlist : liste d'utilisateurs autorisés à utiliser le proxy --> "-" indique que tout le monde peut l'utiliser proxylist : la liste des proxies auxquels se connecter --> a.b.c.d:port,a.b.c.d:port,... (si vous n'indiquez pas de port, c'est le port par défaut -1080- qui est utilisé) Quelques exemples : socks5 - - - - a.b.c.d Ce qu'il y a de plus simple : pour se connecter au serveur SOCKS a.b.c.d. socks5 - - - - a.b.c.d:e.f.g.h Si le premier proxy ne fonctionne pas, c'est le proxy e.f.g.h qui sera utilisé. Notez que lorsque vous spécifiez des proxies, ils ne sont pas tous utilisés : si un proxy ne fonctionne pas, on essaie le suivant, mais en aucun cas on en traverse plusieurs (je ne pense pas que cela soit possible : j'ai longtemps cherché et je n'ai pas trouvé de solution; cependant, il est bien entendu possible qu'il en existe une...). Pour en savoir plus sur ce fichier de configuration, consultez la manpage libsocks5.conf (au fait, les manpages sont situées dans le répertoire man, au cas où vous ne les trouveriez pas ;o). Ensuite, vous pouvez utiliser le script runsocks (situé dans le répertoire shlib) pour socksifier un programme dynamiquement (syntaxe : runsocks [arguments], par exemple "runsocks ftp ftp.freebsd.org"). Le problème est que ce script ne fonctionne pas toujours (il ne semble pas fonctionner sous FreeBSD 4.x). Je suppose que cela vient du fait que ce script n'utilise pas la variable d'environnement LD_PRELOAD. Voici un tout petit script qui remplit exactement le même rôle que runsocks (mais qui fonctionne ;o) : <--- socksify ---> #!/bin/sh # Dynamically socksify a program if [ $(($# < 1)) = 1 ] # There's no argument to socksify then echo "usage: $0 [arguments]" exit 1 fi export LD_PRELOAD="/usr/lib/libsocks5.so" exec $* <--- socksify ---> Le problème de runsocks (et celui de socksify) est qu'il faut spécifier à chaque fois que l'on souhaite socksifier un programme. C'est bien sympathique pour socksifier son client FTP, mais lorsque vous utilisez plusieurs outils de façon très fréquente (lors d'un hack par exemple :o), c'est assez embêtant. La solution à ce petit problème est très simple : setenv LD_PRELOAD "/usr/lib/libsocks5.so" # Dans mon fichier ~/.login J'avoue que c'est une méthode assez brutale, mais elle fonctionne :o). Si cette solution vous paraît trop complexe, il en existe une autre : téléchargez le petit programme bouncer.c sur le site www.hackers-pt.org (vous y trouverez également d'autres programmes intéressants) et compilez-le (n'oubliez pas de lire les indications placées en haut du fichier source). En principe le programme fonctionne sous Linux et *BSD (et sous d'autres Unix). Après compilation, vous obtenez une bibliothèque (bouncer.so). Note : comme nous l'avons vu, la précédente méthode ne permettait pas de traverser plusieurs proxies. Celle-ci le permet et par conséquent c'est elle que je vous conseille d'utiliser (à moins que vous ne souhaitiez utiliser un seul proxy, ce que je vous déconseille si c'est pour faire des actions dites "sensibles" :o). (Autre) note : j'ai jugé utile d'inclure le programme bouncer.c à la fin de ce présent document (voir "VI. Annexe"). Pour utiliser ce "wrapper", vous devez définir deux variables d'environnement. La première, BOUNCER, indique par quels proxies passer : Proxy 1 Proxy 2 ... Bourne shell : export BOUNCER="s:a.b.c.d:1080@s:a.b.c.d:1080@..." C shell : setenv BOUNCER "s:a.b.c.d:1080@s:a.b.c.d:1080@..." - s indique que le proxy est de type SOCKS - 1080 est le port par défaut des proxies SOCKS - a.b.c.d est l'adresse IP du proxy La seconde est LD_PRELOAD, à laquelle on assigne le chemin absolu vers la bibliothèque : setenv BOUNCER "/usr/lib/bouncer.so" # Par exemple Vous pouvez modifier le script socksify pour qu'il fonctionne avec la bibliothèque bouncer.so. Vous pouvez aussi placer les déclarations dans l'un de vos scripts de démarrage, comme nous l'avons vu plus haut. Si vous avez du temps et du courage, et que vous faites une utilisation intensive des proxies SOCKS, n'hésitez pas à créer une série de scripts vous permettant de gérer au mieux tous ces proxies... Enfin, une règle d'or à ne jamais oublier : vérifiez *TOUJOURS* que vous passez bien par les proxies !!! Par exemple, connectez-vous au site ftp.zedz.net (et regardez ce qu'indique la bannière de connexion). De plus, c'est un bon site de hacking, alors profitez-en pour faire un petit tour ;o). Le programme sockstat vous sera lui aussi utile pour vérifier la bonne connexion à un proxy (ce programme est disponible si vous utilisez FreeBSD, sinon je pense -j'espère- que votre système dispose d'un programme plus ou moins similaire). IV. En savoir plus sur SOCKS ----------------------------- Le package de Nec contient quelques documents/RFC relatifs au protocole SOCKS, une FAQ, des manpages, des exemples de fichiers de configuration, de programmes, etc. Leur site (www.socks.nec.com) contient lui aussi quelques ressources intéressantes. V. Conclusion -------------- Les proxies SOCKS sont une méthode de protection assez efficace si elle est utilisée correctement, et désastreuse sinon (car elle vous donnera une fausse sensation de sécurité). J'espère ne pas avoir raconté trop de bêtises dans ce document... Une dernière petite remarque avant de vous laisser : Ce document est placé dans le domaine publique : faites-en ce que vous voulez (ATTENTION : cela ne concerne pas le programme bouncer.c !!!). -- Nitronec, l'Astronaute de Service -*- HELL : http://hell.unixlover.com VI. Annexe : bouncer.c (version 1.1) ------------------------------------- <--- bouncer.c ---> /* bouncer v1.1 connect() client dynamic wrapper to bounce tcp connections by mirage (ptlink/ptnet) .\\idgard Security Services [March/April/May 1999] Some portability fixes added in September MANY thanks to LiquidK @ptnet/promiscnet/efnet this whole .c was based on an example of his... this can be used, for example, to fetch your mail anonymously, to transparently bounce irc, http or even remote exploit connections. actually, anything AF_INET that uses SOCK_STREAM is likely to work. it does this by grabbing calls to connect() and, behind the scenes, use one or more proxies to "bounce" and thus hide the source of the connection (you). tested only in linux, but others should work too, at least with some changes. it doesn't require root priviledges. compilation: linux: gcc bouncer.c -o bouncer.so -ldl -shared -O2 -s bsd: gcc bouncer.c -c -fPIC -O2 -s && ld bouncer.o -o bouncer.so -shared you may need to use -DSOCKLEN, depending on the OS and libs. to use, in bash: export BOUNCER="type:host:port@type:host:port(@...)" export LD_PRELOAD=/path/to/bouncer.so ./your_connect_program where is: 'w' for Wingate 's' for SOCKS4 'c' for CSM Proxy to unload: unset BOUNCER LD_PRELOAD some debugging output can be turned on by setting the environment variable BOUNCER_DEBUG to "1". note that, when using wingate, byte 255 is used as a telnet control caracter, so wingate should not be used in binary connections, not even before a socks bounce for example (socks may use byte 255 in its protocol). AFAIK there is no simple way to fix this, suggestions are welcome. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef SOCKLEN typedef int socklen_t; #endif #define SOCKS4_USER "billgates" #define BOUNCE_NORMAL 0 #define BOUNCE_WINGATE 1 #define BOUNCE_SOCKS4 2 #define BOUNCE_CSM 3 #define YES 1 #define NO 0 #define ERROR -1 char *bounce_env; static void *lib_handle = NULL; /* place to put the old connect() call */ static int (*old_connect) (int sockfd, const struct sockaddr *serv_addr, int addrlen); /* this is not reentrant :) */ int in_use=0; /* whether to print or not some debugging output */ int debug=0; /* get original connect() address and BOUNCER from the environment */ static void init (void) { char *debug_env = getenv ("BOUNCER_DEBUG"); if (debug_env != NULL) if (debug_env[0] == '1') { debug = YES; printf ("bouncer debugging started\r\n"); } bounce_env = getenv ("BOUNCER"); if (!(lib_handle = dlopen ("libc.so", RTLD_LAZY))) if (!(lib_handle = dlopen ("libc.so.6", RTLD_LAZY))) if (!(lib_handle = dlopen ("libc.so.5", RTLD_LAZY))) { printf ("error loading libc!\n"); exit (1); } if (!(old_connect = dlsym (lib_handle, "connect"))) { printf ("connect() not found in libc!\n"); exit (1); } } /* return the number of chars needed represent */ int int_chars (int num) { int i = 1, size = 0; while ((num / i) != 0) { i *= 10; size++; } return size; } /* connect to , using the given */ int connect_to (int sock, char *host, int port) { struct hostent *he = NULL; struct sockaddr_in sa; if ((he=gethostbyname(host)) == NULL) return ERROR; memcpy (&sa.sin_addr, he->h_addr, he->h_length); sa.sin_port = htons (port); sa.sin_family = AF_INET; return old_connect (sock, (struct sockaddr *)&sa, sizeof sa); } /* read chars from to */ int read_sock (int sock, char *dest, int num) { int i; for (i = 0; i < num; i++) if (read (sock, dest+i, 1) == ERROR) return ERROR; dest[i] = 0; return !ERROR; } /* read from until a is received */ int read_until (int sock, char c) { char buf = 255; while (buf != c) if (read (sock, &buf, 1) == ERROR) return ERROR; return !ERROR; } /* bounce through a Wingate */ int bounce_wingate (int sock, char *host, int port) { char buf[80]; if (debug) printf ("wingate in %s:%d\r\n", host, port); snprintf (buf, 80, "%s %d\n", host, port); write (sock, buf, strlen (buf)); if (debug) printf ("request sent\r\n"); if ( (read_until (sock, '\n') == ERROR) || (read_until (sock, '\n') == ERROR) ) return ERROR; if (debug) printf ("done\r\n"); return !ERROR; } /* bounce through a SOCKS4 firewall */ int bounce_socks4 (int sock, char *host, int port) { struct hostent *he = NULL; unsigned char buf[4]; char buf2[100]; if (debug) printf ("socks4 in %s:%d\r\n", host, port); if ((he = gethostbyname (host)) == NULL) return ERROR; memcpy (&buf, he->h_addr, he->h_length); /* taken from eggdrop's net.c:proxy_connect() */ sprintf (buf2, "\004\001%c%c%c%c%c%c%s", (port >> 8) % 256, (port % 256), buf[0], buf[1], buf[2], buf[3], SOCKS4_USER); write (sock, buf2, 8 + strlen (SOCKS4_USER) + 1); if (debug) printf ("request sent\r\n"); if (read_sock (sock, buf2, 8) == ERROR) return ERROR; if (debug) printf ("done\r\n"); return !ERROR; } /* bounce through a CSM Proxy */ int bounce_csm (int sock, char *host, int port) { char buf[100]; if (debug) printf ("csm in %s:%d\r\n", host, port); if (read_until (sock, '>') == ERROR) return ERROR; sprintf (buf, "%s:%d\n", host, port); write (sock, buf, strlen (buf)); if (debug) printf ("request sent\r\n"); if (read_until (sock, '\n') == ERROR) return ERROR; if (debug) printf ("done\r\n"); return !ERROR; } /* decide how to connect */ int bounce_to (int type, int sock, char *host, int port) { switch (type) { case BOUNCE_NORMAL: return connect_to (sock, host, port); case BOUNCE_WINGATE: return bounce_wingate (sock, host, port); case BOUNCE_SOCKS4: return bounce_socks4 (sock, host, port); case BOUNCE_CSM: return bounce_csm (sock, host, port); default: return ERROR; /* should never be reached */ } } /* main bouncer */ int bounce (int sock, struct sockaddr_in *sa) { int last_bounce = BOUNCE_NORMAL; /* last bounce type made */ char *type, *host, *port, *env = bounce_env; /* BOUNCER variable parser */ if ((type = strtok (env, ":")) != NULL) do { if ((host=strtok(NULL, ":")) == NULL) return ERROR; if ((port=strtok(NULL, "@")) == NULL) return ERROR; if (bounce_to(last_bounce, sock, host, atoi(port)) == ERROR) return ERROR; switch (type[0]) { case 'w': last_bounce=BOUNCE_WINGATE; break; case 's': last_bounce=BOUNCE_SOCKS4; break; case 'c': last_bounce=BOUNCE_CSM; break; default: return ERROR; } } while ((type=strtok(NULL, ":")) != NULL); else return ERROR; host = (char *) inet_ntoa (sa->sin_addr); /* connect to the real destination */ last_bounce = bounce_to (last_bounce, sock, host, ntohs (sa->sin_port)); in_use = NO; return (last_bounce); } /* the all mighty wrapper */ int connect (int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen) { int optval = 0, optlen = sizeof (optval), tmp, flags; if (!lib_handle) /* If not loaded, load it now */ init (); /* if it's not connecting to localhost, and socket is AF_INET */ if ( (((struct sockaddr_in *)serv_addr)->sin_addr.s_addr != 16777343) && (((struct sockaddr_in *)serv_addr)->sin_family == AF_INET) ) /* if we're not already being used */ if (!in_use) /* if BOUNCER is defined */ if (bounce_env != NULL) /* if getsockopt() succeds */ if (getsockopt (sockfd, SOL_SOCKET, SO_TYPE, &optval, &optlen) != -1) /* and socket is SOCK_STREAM */ if (optval == SOCK_STREAM) { /* bounce'em baby */ in_use = YES; flags = fcntl (sockfd, F_GETFL); if (flags & O_NONBLOCK) /* if O_NONBLOCK is set */ fcntl (sockfd, F_SETFL, flags-O_NONBLOCK); /* unset it */ tmp = bounce (sockfd, (struct sockaddr_in *) serv_addr); if (flags & O_NONBLOCK) fcntl (sockfd, F_SETFL, flags); /* put O_NONBLOCK back in */ return tmp; } /* otherwise, use normal connect */ return old_connect (sockfd, serv_addr, addrlen); } <--- bouncer.c --->