• STATISTIQUES
  • Il y a eu un total de 1 membres et 919 visiteurs sur le site dans les dernières 24h pour un total de 920 personnes!


    Membres: 2 097
    Discussions: 3 554
    Messages: 32 736
    Tutoriels: 78
    Téléchargements: 38
    Sites dans l'annuaire: 58


  • ANNUAIRE
  • [FR] Forum-Webmaster
    Une communauté webmaster pour apporter / recevoir de l'aide en création de site internet. Webmaster...
    Webmaster
    [EN] xda-developers
    Très bon site pour les gros bidouilleurs de smartphone de windows à androïd et de Apple jusqu'...
    Phreaking
    [EN] SecurityFocus
    SecurityFocus a été conçu pour faciliter la discussion sur des sujets liés la sécu...
    Vulnérabilités
    [EN] This is legal
    Basic: 10, Realistic: 5, Programming: 1, Bonus: 11, SQL: 2, Encryption: 6, Application: 4, User Contributed: 3
    Challenges
    [FR] PHP Débutant
    Apprendre le PHP par l'exemple, facilement et simplement. Réservé d'abord aux débutants....
    Programmation
    [EN] w3challs
    Ce site propose différents types de défis informatiques: piratage, craquage, cryptographie, stég...
    Hacking
    [EN] phrack
    Lot's of stuff !
    Hacking

  • DONATION
  • Si vous avez trouvé ce site internet utile, nous vous invitons à nous faire un don du montant de votre choix via Paypal. Ce don servira à financer notre hébergement.

    MERCI!




[C] Programmation systeme: execve(), fork() et pipe()

Hello everyone !

Bon, et bien aujourd'hui, j'vais vous montrer un peu de programmation système en C sous les systèmes de type Gnu/Linux.
Au programme: execve(), fork(), and pipe().

0x01 - execve()

La fonction execve() est une fonction assez utile qui va nous permettre d’exécuter un autre programme avec ses arguments dans un environnement que l'on pourra spécifier.
Cette fonction est prototypée de la façon suivante:

Code C :

int execve(const char *path, char *const argv[], char *const envp[]);
 


- Le premier paramètre: path est une chaîne de caractère définissant l'emplacement du binaire que l'on veut exécuter. Si vous voulez lancer la commande bien connue "ls", il vous faudra taper tout le chemin jusqu'à celle-ci. Ce qui donnera par exemple: "/bin/ls".

- Le second paramètre représente les arguments qui sont passes au programme. Il contient la même chose que le paramètre "argv" que l'ont retrouve dans la déclaration d'un main(). C'est un tableau de chaines de caractère qui se termine toujours par un pointeur a NULL.

- Le troisième paramètre, souvent moins connu représente l'environnement du programme. Ce paramètre se retrouve aussi dans la déclaration d'un main(). mais pour le spécifier, il faut déclarer notre main tel que:

Code C :

int main(int argc, char *argv[], char *env[]);
 


Le tableau de pointeur env fini lui aussi, comme argv par un pointeur a NULL.

Lorsque vous tapez "ls" dans votre shell, c'est l'environnement du shell qui sera passe a "ls". Vous pouvez visualiser l'environnement en tapant la commande "env" dans votre shell (en tout cas sous bash et tcsh. Je ne sais si c'est différent dans d'autres shell).

Voila pour les paramètres. Passons maintenant a la valeur de retour.
On le voit bien dans le prototype, execve() nous retourne un int. Mais cette fonction est un peu particulière. En effet, si l’exécution de la commande échoue, execve() renverra la valeur -1, et mettra la variable globale errno a la valeur convenu (voir le man d'execve() pour plus de précisions a ce sujet).
Dans le cas contraire, si l’exécution du binaire passé en paramètre s'effectue correctement, execve() ne retournera absolument rien puisqu'il "passe au programme appelé".

Ça peut paraître étrange, mais c'est très logique. Pour exécuter un binaire, le processeur garde un pointeur vers l'instruction en cours d’exécution, et est déplacé automatiquement vers la prochaine instruction. L'appel a execve() va permettre de déplacer se pointeur en allant a l'adresse de début du binaire a exécuter. Le binaire est ensuite exécuter. mais lorsqu'on arrive a la fin, le programme se ferme car le pointeur sur l'instruction en cours ne peut pas revenir tout seul a la position avant l’exécution.

Je ne sais pas si j'ai été très clair, mais voyons un exemple pour éclaircir un peu plus.

Code C :

#include <unistd.h>
#include <stdio.h>

int
main(int argc, char *argv[], char *env[])
{
  if (argc > 1)
    if (execve(argv[1], argv + 1, env) == -1)
      perror("execve");

  printf("My pid is: %d\n", getpid());

  return 0;
}
 


Ce que l'on fait ici:
- on vérifie qu'il y a bien un argument au programme.
- si c'est le cas, on exécute tout en prenant tout comme argument.
- si execve() fail, on affiche un message d'erreur.
- a la suite de tout ça, le programme va afficher son propre pid.

Petite parenthèse sur le pid pour ce qui ne savent pas ce que c'est. Le pid est le numéro d'identification d'un processus. Chaque processus possède le sien. C'est un numéro qui s'incrémente jusqu'à atteindre une valeur seuil, après laquelle il va prendre la première plus petite valeur disponible. Cette valeur est toujours positive.

bien, donc on compile et on lance.

Code :
$> gcc -o example1 example1.c
$> ./example1 test
execve: No such file or directory
My pid is: 17600
$> ./example1 /bin/ls -l
total 56
-rwxr-xr-x 1 ark users 7119 Oct 21 10:50 example1
-rw-r--r-- 1 ark users  229 Oct 21 10:50 example1.c
-rw-r--r-- 1 ark users 3575 Oct 21 10:48 tuto

Comme on peut le voir, la première fois, le programme est lance avec le paramètre "test". execve() fail car le binaire "test" n'existe pas, perror() fait bien son boulot et affiche un message d'erreur. Ensuite, le pid est affiche.
J'ai ensuite lance le programme avec des paramètres valides: "/bin/ls" et "-l". execve() lance bien le binaire "ls", situe dans le répertoire "/bin/", et lui passe bien l'option "-l". En revanche, après l’exécution de la commande, le pid n'est pas affiche.

C'est pas mal de savoir comment exécuter un programme, mais pour en exécuter plusieurs a la suite, il va falloir faire autrement. C'est ici qu'intervient cette magnifique fonction fork()

0x02 - fork() ou comment faire des bébés en C.

Bon, et bien je vais faire ça comme pour execve(), on commence par le prototype.

Code C :

pid_t fork(void);
 


Comme on peut le voir, fork() ne prends pas de paramètre. Mais ce qui va nous intéresser c'est surtout la valeur de retour. fork() renvoi une valeur de type pid_t. Le type pid_t est en réalité un typedef sur un type int. Et ne représente rien d'autre qu'un pid.

Alors cette fonction magique fork(), ce qu'elle fait c'est qu'elle va dupliquer le processus courant.

Globalement, au lancement du programme, toutes les instructions vont être exécuter de manière classique. Lorsque l'ont va arriver au fork(), un nouveau processus identique au premier va être créer. C'est un peu comme si on se retrouvait a avoir lance deux fois le même programme. Sauf que fork() retourne un pid. Et c'est ici que c'est très avantageux: si le pid retourne est égal a 0, on est dans le processus qui vient d’être crée (processus fils). Sinon, le pid est égal au pid du processus fils.

Un petit exemple pour éclaircir tout ça:

Code C :

#include <unistd.h>
#include <stdio.h>

int
main(void)
{
  pid_t          pid;

  if ((pid = fork()) == -1)
    {
      perror("fork");
      return 1;
    }
  else if (pid == 0)
    printf("Je suis le fils, et mon pid = %d\n", getpid());
  else
    printf("Je suis le pere, et mon pid = %d. Le pid de mon fils = %d\n", getpid(), pid);

  return 0;
}
 


J'vous laisse compiler et tester ça hein, vous êtes grands maintenant ! Et je pense que c'est assez clair pour être compris Smile

Okay. maintenant qu'on a vu a quoi sert fork() on va pouvoir faire des trucs utiles avec. Du coup, bah, je vous laisse vous démerder, je veux un programme qui exécute une commande passée en paramètre, et qui affiche son pid après, même quand l’exécution a vraiment lieu. C'est pas si difficile, prenez le temps de le faire pour bien comprendre.

Oh, wait ! (c'est le cas de le dire :') ) Vous allez avoir besoin d'une autre fonction: wait()
Cette fonction, globalement, permet d'attendre la fin d'un processus. Lisez bien le man (man 2 wait). J'vais pas tout vous donner non plus hein ! Tongue Cherchez par vous même un peu bande de larves !

Il y a la correction la, juste en dessous... Mais si vous avez pas fini, continuez ! C'est surtout en faisant les choses par soi même qu'on apprends.

Bon, la correction est la:

Code C :

#include <unistd.h>
#include <stdio.h>

/* Includes necessaires pour la fonction wait() */
#include <sys/types.h>
#include <sys/wait.h>

int
main(int argc, char *argv[], char *env[])
{
  pid_t           pid;
  int           status;

  if (argc > 1)
    {
      if ((pid = fork()) == -1)
        {
          perror("fork");
          return 1;
        }
      /* Si pid == 0, alors on est dans le process fils. */
      else if (pid == 0)
        {
          if (execve(argv[1], argv + 1, env) == -1)
            perror("execve");
          return 1; /* On termine le fils même si execve fail parce qu'on veut voir que le pid du pere*/
        }
      /* Sinon, dans le pere. */
      else
        wait(&status); /* Oui, il faudrait vérifier la valeur de retour... */
    }

  printf("My pid is: %d\n", getpid());

  return 0;
}
 


Bon, ben c'est pas très complique hein Wink
- on fork() et vérification de la valeur de retour. (Oui, comme beaucoup de fonction, fork() peut fail)
- si pid == 0, on est dans le fils, et on exec la commande passée en paramètre.
- sinon, si on est dans le père, on wait(). En effet, il faut attendre la fin du processus fils avant d'aller print notre pid.
- L’exécution de la commande passée a execve() est fini, wait() n'est plus bloquant. on affiche notre pid puis on quitte.

Globalement c'est tout pour fork(). J’espère que vous avez compris, sinon faut pas hésiter a faire des tests pour bien comprendre, et je me répète, mais lire les man, c'est vraiment bien aussi ! Bref, je vais maintenant vous faire découvrir un truc super utile, qui permet a deux processus de se passer des données: le pipe. (Et pas de blagues foireuses, avec pipe, merci ! xD)

0x03 - Pipe: transfert de données entre 2 processus.

Alors, un pipe en soi, c'est comme un tuyau, avec une entrée et une sortie par lequel on peut faire passer des données. Vous en avez peut être déjà utilise dans un shell, quand par exemple on fait un "ls -l | wc -l" pour compter le nombre de ligne qu'a écrit la commande "ls -l".
En fait, tout processus possède une entrée standard, et deux sorties: une sortie standard, et une sortie d'erreur. Ces entrées et sorties sont représentées par des nombres qu'on appelé des "file descriptor" (que j’abrégerais "fd").

A retenir:

0 = entre standard.
1 = sortie standard.
2 = sortie d'erreur.

Dans le cas de la commande "ls -l | wc -l", la sortie standard de "ls -l" est remplacée par le début du pipe. Et l’entrée standard de "wc -l" est remplacée par l'autre bout du pipe. Du coup, les données qu'affiche la commande "ls" sont redirigées vers le pipe et récupérées par "wc".

Pour écrire ou lire sur un file descriptor particulier, on peut utiliser les fonctions: write() et read() dont le premier paramètre est le fd. Attention, on ne read() jamais sur une sortie ! Merci de pas faire des trucs moches comme çà. x)

Bon, donc comme je le disais. on a besoin de deux processus. Et on va avoir besoin d’écrire et lire sur un pipe. Je vous montre comment utiliser pipe(), et ensuite je vous laisserais vous démerder un peu, une fois de plus Smile

So, prototype (man pipe):

Code C :

int pipe(int pipefd[2]);
 


Valeur de retour, c'est facile, 0 si ça a réussi, -1 si ça a échoue.
Le plus intéressant reste le paramètre: int pipefd[2]. C'est un tableau d'int de 2 cases, qui va permettre de créer le pipe. On aura un file descriptor dans chaque case. Il faudra lire sur le pipefd[0] et écrire sur le pipefd[1].

Juste une dernière chose avant de vous lâcher dans le code comme des zombies enrages, vous devez impérativement fermer l’extrémité du pipe que vous n'utilisez pas dans un processus. Pour cela, il vous faudra utiliser la fonction close(), qui prends en paramètre le fd a fermer. Je ne suis pas encore sur exactement du "pourquoi" vous devez faire ça, mais j’éditerais quand je le saurais.

Okay, alors, un petit exo maintenant: Réalisez un programme qui prends en paramètre une chaîne de caractère, et qui va faire un fork() et qui dans son processus fils va envoyer via un pipe la chaîne de caractère au processus père.

Encore une fois, forcez vous a chercher !

Correction:
Code C :

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>

int
main(int argc, char *argv[], char *env[])
{
  pid_t          pid;
  int            status;
  int            pipe_fd[2];

  if (argc < 2)
    return 1;

  if (pipe(pipe_fd) == -1)
    {
      perror("pipe");
      return 1;
    }

  if ((pid = fork()) == -1)
    {
      perror("fork");
      return 1;
    }
  else if (pid == 0)
    {
      close(pipe_fd[0]);
      write(pipe_fd[1], argv[1], strlen(argv[1]));
      close(pipe_fd[1]);
      return 1;
    }
  else
    {
      char          buffer[1024];
      int           ret;

      close(pipe_fd[1]);
      while ((ret = read(pipe_fd[0], buffer, 1023)))
        {
          buffer[ret] = 0;
          printf("%s\n", buffer);
        }
      close(pipe_fd[0]);
    }

  return 0;
}
 


Rien de très complique ici non plus si vous avez compris le fonctionnement. J'attire tout de même votre attention sur les close() a la fin des 2 processus, en effet, ces fd ne seront plus utilises, on peut donc les fermer.


0x04 -- dup() / dup2(): dupliquer, rediriger des flux

La fontions dup() permet de dupliquer un file descriptor, voyons son prototype:

Code C :

int dup(int fildes);
 


Le paramètre est un file descriptor indiquant le fd a dupliquer. La valeur de retour est -1 si un erreur est survenue xor un nombre entier qui indique le nouveau file descriptor crée.

Petit schéma:

Code :
fildes ---------------+----------> ressource
                      |
valeur_de_retour -----+

Ici, la 'ressource' va etre le fichier / pipe / entrée std / sortie std /etc... dont le fd est fildes. La fonction dup() va créer et renvoyer le fd 'valeur_de_retour'
On aura accès a la ressource via ces 2 fd.

La fonction dup2() quand a elle ne va pas dupliquer le fd, mais le remplacer par un autre.

Code C :

int dup2(int fildes, int fildes2);
 


Donc, ici, attention a ne pas confondre les 2 paramètres. Le premier correspond au nouveau fd, le 2 eme est l'ancien, a remplacer.

La valeur renvoyer est celle de l'ancien file descriptor, ou -1 en cas d'erreur. (Pour plus de précision, voir le man)

Du coup, un petit schéma pour y voir mieux:

Code :
fildes2 ---  (dup2 coupe l'access)     --+------------> ressource
                                         |
fildes ----------------------------------+

Du coup, dans ce cas, le fildes2 est fermer par close(). Inutile me direz vous ? En effet on ne fait que remplacer un nombre par un autre... Mais cela va permettre de faire un lien, par exemple entre un pipe et une sortie standard, ou un pipe et une entrée standard, etc...

Ce qui fait que avec ça, on va pouvoir rediriger ce qu'écrit un programme vers une variable a nous, ou bien rediriger ce qu'écrit un programme vers l'entrée d'un autre. C'est ce qui permet de faire:

Code :
$> ls -la | wc -l

Je vous propose donc un petit exercice: faire un soft qui prends en paramètre le path d'une commande (comme pour mes autres exemples) et faire en sorte que ce que cette commande affiche soit récupéré dans une variable que vous afficherais.

Bon courage ! Tongue

[...]

Correction !

Code C :

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>

int
main(int argc, char *argv[], char *env[])
{
  pid_t         pid;
  int           pipe_fd[2];

  if (argc < 2)
    return 1;

  if (pipe(pipe_fd) == -1)
    {
      perror("pipe");
      return 1;
    }

  if ((pid = fork()) == -1)
    {
      perror("fork");
      return 1;
    }
  else if (pid == 0)
    {
      close(pipe_fd[0]);
      if (dup2(pipe_fd[1], 1) == -1)
      perror("dup2");
      else if (execve(argv[1], argv + 1, env) == -1)
      perror("execve");
      return 1;
    }
  else
    {
      char      buffer[1024];
      int       ret;
      int       status;

      close(pipe_fd[1]);

      while ((ret = read(pipe_fd[0], buffer, 1023)) != 0)
      {
        printf("%d\n", ret);
        buffer[ret] = 0;
        printf("%s\n", buffer);
      }
    }

  return 0;
}
 


Vous pouvez essayer de faire un autre exo (sans correction cette fois ci, j'ai la flemme Tongue) ou vous passerez ce qu'affiche une commande a l’entrée d'une autre commande. C'est a dire reproduire le comportement d'un "ls -l | wc -l" par exemple.

C'est donc après toutes ces petites explications que s’achève ce cours.
Critiques, remarques, questions, here we go !
21-10-2012, 20h40
Message : #1
aulos7 Hors ligne
Membre
*



Messages : 49
Points: 3
Inscription : Oct 2012
RE: [C] Programmation systeme: execve(), fork() et pipe()
C'est très intéressant ( même si je n'ai pas encore tout saisi).
21-10-2012, 23h30
Message : #2
sakiir Hors ligne
[sakiir@Ubuntu]:~$ ./ExploitMe ShellC0de
*



Messages : 411
Points: 34
Inscription : Sep 2012
RE: [C] Programmation systeme: execve(), fork() et pipe()
De même pour moi ! je pense que tu as très bien expliqué , tout comme le type qui a écris le bouquin que j'ai .. mais j'ai du mal avec ça ..

Veuillez vous enregistrer pour visualiser l'ensemble du forum en cliquant ici.
21-10-2012, 23h39
Message : #3
ark Hors ligne
Psyckomodo!
*****



Messages : 1,033
Points: 317
Inscription : Sep 2011
RE: [C] Programmation systeme: execve(), fork() et pipe()
Merci pour vos commentaires, hésitez pas a poser des questions hein ;) Et allez y a petites doses.
Je repasserais dessus demain pour ajouter des informations supplémentaires que j'ai quelque peu oublié... :p Mais rien de grave rassurez vous :p J'en avais juste un peu marre a la fin, j'ai abrégé. ^^'
22-10-2012, 09h55
Message : #4
aulos7 Hors ligne
Membre
*



Messages : 49
Points: 3
Inscription : Oct 2012
RE: [C] Programmation systeme: execve(), fork() et pipe()
Ben c'est pas de ta faute si je n'ai pas encore tout compris : il faudrait que je me remette à un langage de plus bas niveau pour tout saisir et faire des tests.
23-10-2012, 20h15
Message : #5
sakiir Hors ligne
[sakiir@Ubuntu]:~$ ./ExploitMe ShellC0de
*



Messages : 411
Points: 34
Inscription : Sep 2012
RE: [C] Programmation systeme: execve(), fork() et pipe()
ouai aulos ! en te remttant a quelque choose de plus bas niveau .. tu finis par comprendre des choses plus facilement Smile

Veuillez vous enregistrer pour visualiser l'ensemble du forum en cliquant ici.
26-10-2012, 21h34
Message : #6
ark Hors ligne
Psyckomodo!
*****



Messages : 1,033
Points: 317
Inscription : Sep 2011
RE: [C] Programmation systeme: execve(), fork() et pipe()
Plop, j'ai rajouter la partie concernant les fonctions dup() et dup2(). btw, j'ai appris au passage l'existence d'une fonction dup3(), peut être que je la rajouterais Tongue
28-10-2012, 12h45
Message : #7
spin Hors ligne
Contributeur
*****



Messages : 325
Points: 38
Inscription : Nov 2011
RE: [C] Programmation systeme: execve(), fork() et pipe()
Dans la section 0x01 :
Citation :Dans le cas contraire, si l’exécution du binaire passé en paramètre s'effectue correctement, execve() ne retournera absolument rien puisqu'il "passe au programme appelé".
Citation :Ça peut paraître étrange, mais c'est très logique. Pour exécuter un binaire, le processeur garde un pointeur vers l'instruction en cours d’exécution, et est déplacé automatiquement vers la prochaine instruction. L'appel a execve() va permettre de déplacer se pointeur en allant a l'adresse de début du binaire a exécuter. Le binaire est ensuite exécuter. mais lorsqu'on arrive a la fin, le programme se ferme car le pointeur sur l'instruction en cours ne peut pas revenir tout seul a la position avant l’exécution.

Je ne comprends pas bien, ici. Qu'advient-il exactement du programme qui a passé sa main ? Il reste en suspens jusqu'à ce que le programme lancé avec execve() se termine et continue ensuite, ou bien il continue son exécution en parallèle (dans le cas où -1 n'est pas retourné) ? Je précise que je ne connais pas la fonction execve(), je ne l'ai jamais utilisé.

Pour le reste, je suis.

Bonne continuation, c'est un bon tuto qui mérite sa place Wink
28-10-2012, 12h53
Message : #8
ark Hors ligne
Psyckomodo!
*****



Messages : 1,033
Points: 317
Inscription : Sep 2011
RE: [C] Programmation systeme: execve(), fork() et pipe()
Hum.. Okay, je vois ce qui te dérange Smile
En fait, tu reste toujours dans le même processus. Tu vas juste passer sur un autre bout de code exécutable. Du coup, quand le programme appelé se termine, le processus se termine aussi. Le programme ayant appelé le second ne continue pas, il s’arrête au niveau de l'appel a execve(). Et du coup, quand le processus se termine, les 2 programmes sont finis.
Ensuite, si execve() renvoi -1, c'est que l'on est pas passe au programme suivant. Le programme appelant continue donc.

New Project News White Hat Hacker V2.3
Accueil | Challenges | Tutoriels | Téléchargements | Forum