• STATISTIQUES
  • Il y a eu un total de 2 membres et 3777 visiteurs sur le site dans les dernières 24h pour un total de 3 779 personnes!


    Membres: 2 604
    Discussions: 3 579
    Messages: 32 816
    Tutoriels: 78
    Téléchargements: 38
    Sites dans l'annuaire: 58


  • ANNUAIRE
  • [FR] Newbie Contest
    Crackme: 35, Cryptographie: 49, Hacking: 27, Javascript/Java: 17, Logique: 31, Programmation: 23, Stéganographie: 53
    Challenges
    [EN] Big-Daddy
    Big-Daddy est site internet communautaire avec un effectif diversifié, y compris des artistes, des programmeur...
    Hacking
    [FR] Root-Me
    Notre équipe se base sur un constat : à l'heure actuelle ou l'information tend à devenir...
    Hacking
    [FR] apprendre-a-manipuler
    Site d'apprentissage de la manipulation d'autrui.
    Hacking
    [EN] Rankk
    Site de challenge construit sur le principe d'une pyramide à 9 level. Level 1: 60,Level 2: 72,Level 3: 68,Lev...
    Challenges
    [EN] Astalavista
    Un site aux ressources incontournable depuis plusieurs années, Astalavista est réellement devenue un cl...
    Hacking
    [FR] Cyber-Hacker
    CH - Cyber Hacker est un jeu par navigateur de simulation de hack, programmez et envoyez vos virus et piratez les aut...
    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!




Note de ce sujet :
  • Moyenne : 4.5 (2 vote(s))
  • 1
  • 2
  • 3
  • 4
  • 5
Introduction a LD_PRELOAD
23-06-2012, 13h43 (Modification du message : 08-12-2012, 17h21 par ark.)
Message : #1
ark Hors ligne
Psyckomodo!
*****



Messages : 1,033
Sujets : 48
Points: 317
Inscription : Sep 2011
Introduction a LD_PRELOAD
Ce tuto va expliquer le fonctionnement de la variable d'environnement LD_PRELOAD sous GNU/Linux (probablement pareil sous Unix, mais j'ai pas testé :p)

1 - Qu'est ce que LD_PRELOAD

Pour exécuter un programme sur un système, ce programme est avant tout recopié en mémoire. Cependant, il arrive très souvent qu'il utilise des libs externes, comme par exemple la libc, la libqt, ou bien d'autres encore...
Prenons le cas d'un programme simple en C:

Code C :

  #include <string.h>
  #include <stdio.h>

  int
  main(int argc, char *argv[])
  {
    if (argc == 2)
    {
        printf("%d\n", strlen(argv[1]));
    }
    return 0;
  }

 


Ce code va écrire la longueur de la chaîne passée en paramètre. Vous pouvez le voir c'est très basique. Cependant, ce code fait 2 appels a des fonctions externes: printf et strlen.

Au moment du lancement du programme, la première chose qui sera effectué sera de retrouver ces fonctions dans les libs installés.
D'ailleurs, copiez ce code et compilez le.
Un petit détail pour la compilation: utilisez l'option -fno-builtin de gcc sinon le compilo remplacera automatiquement l'appel a strlen par une builtin directement dans le code, ce qui ne permettra pas le fonctionnement de la suite de ce tuto.

Puis, lancez le avec strace: strace ./prgm
Vous pouvez voir ces lignes:

Code :
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
  mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f9eaf044000
  access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
  open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
  fstat(3, {st_mode=S_IFREG|0644, st_size=91798, ...}) = 0
  mmap(NULL, 91798, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f9eaf02d000
  close(3)                                = 0
  access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
  open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
  read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200\30\2\0\0\0\0\0"..., 832) = 832
  fstat(3, {st_mode=S_IFREG|0755, st_size=1802936, ...}) = 0
  mmap(NULL, 3917016, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f9eaea67000
  mprotect(0x7f9eaec1a000, 2093056, PROT_NONE) = 0
  mmap(0x7f9eaee19000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b2000) = 0x7f9eaee19000
  mmap(0x7f9eaee1f000, 17624, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f9eaee1f000

Ici, on peut voir tous les accès fait aux libs, a la recherche des fonctions. Si la fonction n'existe pas, le programme ne pourra être lance, et une erreur surviendra.
La fonction access() avec le flag F_OK permet de vérifier si le fichier existe (man 2 access). Si il existe, il est ensuite ouvert avec la fonction open() (voir man 2 open) puis la lib est lu avec la fonction read() (man 2 read ^^)

Bon, voila pour le fonctionnement de base du lancement d'un programme ^^

LD_PRELOAD dans tout ca, et bien ca va nous permettre de set une (ou plusieurs) libs qui vont être load avant la recherche dans les libs systèmes. C'est peut etre pas très clair, mais en gros, ca permet de remplacer une fonction du système par une autre que vous aurez codé vous même.

2 - MISE EN PRATIQUE

Bon, après avoir vu vite rapidement a quoi servait LD_PRELOAD, on va faire mumuse avec, parce que y a pas de raison qu'on le fasse pas. x)

Gardez votre petit programme compilé de tout a l'heure de cote, on va en avoir besoin.
Mais avant ça, ben moi je trouve que c'est un peu nase d'afficher juste un nombre correspondant au nombre de caractères... Et si on faisait en sorte que ca print: "chaîne = 6" plutot ?
Facile, nan ? Ben bien sur. Mais sans toucher au code source, vous en pensez quoi ? Big Grin

Bon, tout ca pour revenir sur LD_PRELOAD.
Rappelez vous, le soft appel strlen, et print le résultat avec printf. Ben on va cheater strlen pour qu'il print aussi la chaîne qui lui est passée en paramètre.

Pour le faire, z'allez m'ouvrir un nouveau fichier C, et dedans vous allez copier coller le prototype de strlen qui se trouve dans le man, vous me virez ce ';' et vous rajoutez des accolades.

Code C :

  #include <string.h>

  size_t strlen(const char *s)
  {

  }
 


Bien ! Donc avec ca, et ben on doit retourner la longueur de la chaîne.
Mais attention, si on utilise la vrai fonction strlen, c'est la notre qu'on va rappeler ! Récursivité infini = Segmentation fault. Ca serait dommage, parce que c'est pas le but. x)
Du coup, comment on va faire ? Ben vous allez me coder un strlen en 4 lignes tas d'feignasse !
C'est fait ? Nan, parce que je vous file la solution si vous voulez, mais patientez un moment encore... Parce que cette fonction que vous codez, ben faites la en dehors de celle que vous avez ^^ Sinon, meme problème, récursivité inf...

Code C :

  #include <string.h>

  size_t
  my_strlen(const char *s)
  {
    size_t  i = 0;

    while (s[i])
       ++i;
    return i;
  }

  size_t
  strlen(const char *s)
  {
    /* On va print notree chaine ici */

    /* On appelle notre fonction my_strlen */
    return my_strlen(s);
  }
 


Voila !
Bon, ben pour print, c'est facile ! Mais... On va juste pas utiliser printf. Pourquoi ? Ben parce que printf fait des appels a strlen. RÉCURSIVITÉ INFINIE AGAIN !
Comment on fait ? ben write(1, s, my_strlen(s));
Pourquoi utiliser write() ? Alors pour 2 raisons. La première, c'est que write() est une fonction qui réalise un appel système au niveau du kernel, c'est donc une fonction que l'on pourrait qualifier de "basique", en effet, elle ne fait en aucun cas appel a d'autres fonctions (comme par exemple strlen()). Et la deuxième raison, c'est qu'on lui donne directement la longueur de la chaîne passé en paramètre, d'ou le strlen(s) en 3em paramètre. Le premier paramètre est un file descriptor (fd), le 1 correspond a la sortie standard, c'est a dire la ou est affiché le texte. Pour plus de détail, allez voir le man 2 write(). (lisez les mans, c'est bon pour la santé ! )

Et voila !
Sauf que la ca va etre moche un peu... rajoutez un write(1, " = ", 3); juste en dessous du premier write, et c'est good !

man write pour ceux qui savent pas comment l’utiliser hein Wink

code final:

Code C :

  #include <string.h>
  #include <unistd.h> /* Ne pas oublier les includes pour write  */

  size_t
  my_strlen(const char *s)
  {
    size_t  i = 0;

    while (s[i])
       ++i;
    return i;
  }

  size_t
  strlen(const char *s)
  {
    write(1, s, my_strlen(s));
    write(1, " = ", 3);
    return my_strlen(s);
  }
 


Compilez ensuite ce fichier de la maniere suivante:

gcc -fPIC -shared lib.c -o lib.so

lib.c étant le fichier que vous venez de coder. L'option -shared sert a dire a gcc que vous compilez une lib partagée, en '.so' donc. (Pour -fPIC, je sais pas, mais il pète une erreur sinon xD)
Pour charger notre lib.so au lancement du prog, on va faire simplement:

env LD_PRELOAD="./lib.so" ./prog argument

Observez le résultat Big Grin

Voila, c'etait une petite introduction a l'utilisation de LD_PRELOAD.

Bon, maintenant qu'on sait faire ca, imaginez un programme qui stocke un mot de passe dans une zone mémoire. Imaginez ensuite que ce mot de passe soit free() a la fin, ou mieux, imaginez que le programme fasse un strcmp dessus... Wink
Voila voila, je vous laisse imaginer tout ce que l'ont peux faire avec ca !

Questions ? Commentaires ? Critiques ? Je prends tout ! Smile
+1 (3) -1 (0) Répondre
23-06-2012, 14h42 (Modification du message : 23-06-2012, 14h59 par spin.)
Message : #2
spin Hors ligne
Contributeur
*****



Messages : 325
Sujets : 15
Points: 38
Inscription : Nov 2011
RE: Introduction a LD_PRELOAD
Le contenu est bien ; peut-être un peu léger mais néanmoins pertinent, étant donné que ça permet pas mal d'applications si j'en crois ta conclusion.
J'aime bien ce genre de tutoriels, qui n'explique pas au lecteur comment faire un truc en particulier, mais plutôt qui lui enseigne une notion « brique », grâce à laquelle le lecteur pourra faire les trucs qu'il voudra par la suite, en réfléchissant et non pas en suivant à la lettre un tutoriel. C'est un bon point.

Mais je suis moins fan de la forme, par contre. C'est une approche très TP, qui manquerait peut-être de quelques propos ; on découvre uniquement par les exemples, j'ai l'impression.
Mais c'est un avis subjectif et qui n'engage que moi, bien-sûr. Certains aimeront certainement cette approche learn by doing, mais pas moi.

Je ne connaissais pas, sinon. Mais maintenant je connais. Bon tutoriel Wink

P.-S. : programmer en assembleur avec les syscalls directement, y a que ça de vrai de toute façon :p
+1 (0) -1 (0) Répondre
23-06-2012, 15h16
Message : #3
ark Hors ligne
Psyckomodo!
*****



Messages : 1,033
Sujets : 48
Points: 317
Inscription : Sep 2011
RE: Introduction a LD_PRELOAD
Merci Spin pour le commentaire Smile Je verrais ce que les autres en pense ! Au niveau de la forme, je sais pas trop, pour ma part je prefere partir sur des exemples et comprendre a partir d'eux. Mais ca depent de chacun effectivement.

Sinon, coder en assembleur, ben j'y ai penser, mais j'ai pas encore le skill... x)
+1 (0) -1 (0) Répondre
23-06-2012, 16h35
Message : #4
supersnail Hors ligne
Éleveur d'ornithorynques
*******



Messages : 1,608
Sujets : 71
Points: 466
Inscription : Jan 2012
RE: Introduction a LD_PRELOAD
Perso, je trouve que c'est un bon tuto, même s'il est un peu trop "SDZ-like" à mon goût, et qu'il faudrait un peu plus étoffer les explications sur le rôle de LD_PRELOAD (ce que ça fait concrètement, etc ...), vu que c'est la "star" du tuto.
Une approche "Explications - illustration" serait préférable je pense Wink

Sinon, essaie de soigner un peu l'ortheaugrafe, parce qu'il y a quelques fautes qui piquent un peu les yeux Wink
Mon blog

Code :
push esp ; dec eax ; inc ebp ; and [edi+0x41],al ; dec ebp ; inc ebp

"VIM est merveilleux" © supersnail
+1 (0) -1 (0) Répondre
23-06-2012, 17h32
Message : #5
ark Hors ligne
Psyckomodo!
*****



Messages : 1,033
Sujets : 48
Points: 317
Inscription : Sep 2011
RE: Introduction a LD_PRELOAD
Ouais, dsl pour les fautes, j'ai pas trop fait gaffe, et j'ai pas d'accents sur le qwerty...
Sinon, pour le "style", j'vais essayer de modif un peu aussi alors.
J'editerais tout ca lundi soir je pense Wink
+1 (0) -1 (0) Répondre
26-06-2012, 17h39 (Modification du message : 26-06-2012, 17h47 par Dobry.)
Message : #6
Dobry Hors ligne
Tueur de lamouz
*



Messages : 206
Sujets : 25
Points: 73
Inscription : Aug 2011
RE: Introduction a LD_PRELOAD
Petite explication fort symathique, je connaisais que le concept du LD_PRELOAD, sans jamais l'avoir utilisé, après m'être penché sur le sujet, je me suis demandé s'il était possible de remplacer une fonction standard par une fonction codée en assembleur (nasm dans mon cas).
Je me suis donc battu (probablement à cause de fautes d'innatention) pour arriver à un résultat tout à fait satisfesant, et très simple (quelques notions en assembleur, et le même code C que celui utilisé dans l'explication d'Ark.
Commencons par le code assembleur:

Code :
externes:
     global strlen ; Declaation de la fonction

strlen:
  push ebp ; Sauvegarde d'EBP
  mov ebp, esp
  push esp ; Sauvegarde d'ESP
      
  mov ebx, [ebp+8] ; Pointeur vers les données
  xor eax,eax ; Compteur

  strlen_loop:
    inc eax
    cmp byte [ebx+eax],0 ; Fin de la chaine
    jne strlen_loop
  
pop esp ; Restauration d'ESP
pop ebp ; Restautation d'EBP
ret

Le but n'est pas de s'attacher sur le fonctionnement de cette fonction, mais je vais tout de même expliquer deux trois détails pour ne pas laisser de trou noir dans l'esprit de certains personnes n'ayant jamais fait d'assembleur.

Code :
externes:
    global strlen
Cette partie du code permet de créer un label qui sera accesible depuis l'exterieur du code assembleur (pour faire simple, nous pourrons y acceder depuis notre fichier codé en C)

Code :
push ebp ; Sauvegarde d'EBP
      mov ebp, esp
      push esp ; Sauvegarde d'ESP

Ce code permet de créer une nouvelle section sur la stack, c'est une notion qui demande un minimum de connaissance sur le fonctionnement de la mémoire (enfin bon, google vous expliquera cela probablement plus correctement que moi)

Code :
mov ebx, [ebp+8] ; Pointeur vers les données
  xor eax,eax ; Compteur

Lorsque vous appellez une fonction en C, les arguments passé à cette dernière sont empilé sur la stack, [ebp+8] signifie que l'on souhaite récupérer la VALEUR placé à l'adresse pointée par ebp+8, pourquoi +8 ?
Encore une fois, je ne devrais pas m'attarder sur cela, ce n'est pas le but de cette explication, mais je trouve dommage de laisser ce bout de code inexpliqué.
Comme je le disais, lorsque qu'en C vous appelez une fonction, les paramètre sont empilés sur la stack et puis l'instruction "call" (qui permet d'accder à la fonction) empile également l'adresse de retour (sinon vous ne reviendriez jamais dans la boucle principal)
Schematiquement (baclé) vous auriez : main() -> push word* (on place le pointeur vers le char* sur la stack) -> push cs (registre qui stocke la prochaine instruction) -> jmp strlen
Ainsi on se retrouve avec sur la stack
ebp+4 : char*
ebp : cs
(oui +4 car stocké sur 32bits soit 4octets)
De plus à l'intérieur de la fonction, lors de la création du segment, on rajoute sur cette stack la valeur d'EBP, la stack ressemble donc à quelque chose comme
ebp+8; char*
ebp+4: cs
ebp : ebp (oui oui !)
Ainsi le pointeur est bien contenu au niveau ebp+8 sur la stack
Bon suite à ces explications peu claires, je continue sur LD_PRELOAD
Pour compiler ce code assembleur, utilisez nasm:
nasm -f elf function.asm
Un nouveau fichier: function.o devrait apparaitre, nous devons maintenant le lier pour en faire une librairie que nous passerons en argument à LD_PRELOAD
ld -shared -o function.so function.o
Nous avons donc maintenant notre "shared library".
en utilisant le même code que celui donné par Ark, vous devriez obtenir un résultat similaire à celui attendu avec la fonction de la librairie standard.

Il y a quelque chose que je souhaiterais rajouter également à ces explications, en effet, nous n'avons aucun moyen (si ce n'est d'observer ) que c'est bel est bien notre fonction qui est chargée et non pas celle standard, pour cela, je vous propose de coller le code suivant (très moche) dans une .asm et recompiler le tout :
Code :
externes:
    global strlen

strlen:
   mov eax,3
   ret
Ce code est celui d'une fonction strlen qui retourne tout le temps 3, si write ne vous affiche que 3 caractère sur une chaine > 3 caractères alors c'est bien votre fonction qui est chargée.
(ah oui, les retour des fonctions sont stockés dans le registre EAX la plupart du temps (tout le temps ?))

Necromoine, n'hésitez surtout pas à me corriger, à me poser des questions, si des choses sont fausses/incomprises dans ce que j'ai écrit (mes explications sont souvent très confuses)
Aestuārium Erudītiōnis

There are only two hard things in Computer Science: cache invalidation, naming things, and off-by-one errors.
+1 (0) -1 (0) Répondre
26-06-2012, 18h05
Message : #7
spin Hors ligne
Contributeur
*****



Messages : 325
Sujets : 15
Points: 38
Inscription : Nov 2011
RE: Introduction a LD_PRELOAD
> (ah oui, les retour des fonctions sont stockés dans le registre EAX la plupart du temps (tout le temps ?))

C'est la convention des compilateurs C (dont gcc bien-sûr), mais c'est aussi une convention pour les gens qui codent en assembleur. Cela permet, lors de la lecture d'un code asm, de repérer aisément ce qui doit être retourné par une fonction.
+1 (0) -1 (0) Répondre
26-06-2012, 18h50
Message : #8
Dobry Hors ligne
Tueur de lamouz
*



Messages : 206
Sujets : 25
Points: 73
Inscription : Aug 2011
RE: Introduction a LD_PRELOAD
Ah d'accord, je ne savais pas d'ou venait cette convention, sinon spin, j'ai eut des echos comme quoi mon explication sur la mémoire était peu claire (ce qui est vrai, quand je me re-lis, c'est n'importe quoi xD) si tu as quelque chose à ajouter n'hésite pas !
Aestuārium Erudītiōnis

There are only two hard things in Computer Science: cache invalidation, naming things, and off-by-one errors.
+1 (0) -1 (0) Répondre
26-06-2012, 19h17
Message : #9
spin Hors ligne
Contributeur
*****



Messages : 325
Sujets : 15
Points: 38
Inscription : Nov 2011
RE: Introduction a LD_PRELOAD
J'avoue que j'ai un peu lu vite, puis comme je connais tout ça je me rends pas trop compte de si c'est clair ou pas. Mais il est vrai que certaines parties sont sombres ou alors un vocabulaire un peu ambiguë du genre « Ce code permet de créer une nouvelle section sur la stack ». Je pense qu'on pourrait avoir plus intuitif et moins technique, un truc plus grossier : « ce code permet de sauvegarder le contexte du programme actuel, avant l'exécution d'un sous programme (ou fonction) ». C'est peu précis mais je pense que ça parle déjà plus à quelqu'un qui n'a jamais fait d'asm. Puis c'est plus imagé. Puis le lecteur peut garder l'idée intuitive qu'il a déjà d'une pile. Ou bien il faut des schémas, mais bon :p

Pour le reste, je pense qu'il est inutile d'expliciter plus si on s'adresse à des non programmeur assembleur, et garder des propos très imagés dans un soucis de vulgarisation.
Sinon, un truc sympa pourrait être d'inviter le lecteur à faire un `gcc -S` pour voir comment se passe l'appel de fonction etc. C'est la méthode de Jon Erikson qui m'a appris l'assembleur the hard way mais ça marche Big Grin
+1 (0) -1 (0) Répondre
28-06-2012, 12h36
Message : #10
spin Hors ligne
Contributeur
*****



Messages : 325
Sujets : 15
Points: 38
Inscription : Nov 2011
RE: Introduction a LD_PRELOAD
Voilà que j'ai relu le tutoriel, c'est bien meilleur à présent Wink
J'ai quand même deux petites remarques, pour ma part.

Citation :Bon, ben pour print, c'est facile ! Mais... On va juste pas utiliser printf. Pourquoi ? Ben parce que printf fait des appels a strlen. RÉCURSIVITÉ INFINIE AGAIN !
Comment on fait ? ben write(1, s, my_strlen(s));
Là je pense qu'il faudrait expliquer et justifier l'utilisation de write(). On a l’impression que c'est évident, mais le néophyte ne sait peut-être pas que write() est l'implémentation C d'un appel système du kernel (si on peut le dire ainsi), ce qui en fait alors une fonction « élémentaire ». Et aussi, peut-être qu'on peut dire deux mots quant à ses paramètres. Le lecteur va se demander pourquoi il passe un 1 en premier paramètre ou pourquoi il passe my_stren(s) aussi. Là je pense qu'un petit extrait de `man 2 write` ne ferait pas de mal Wink

Seconde remarque, moins important à mon avis. Tu invites le lecteur à faire un `strace`, et c'est une excellente idée selon moi. Mais là encore, tu dis qu'on peut voir l'accès au libs comme si c'était évident. Le néophyte peut être perdu avec toutes ces lignes, peut-être voudra-t-il une indication sur les lignes importantes, celle qui appellent vraiment les libs. Je pense que l'output de strace mérite plus d'explications, pas forcément détaillées mais au moins de quoi montrer au lecteur comment se passe l'appel d'une lib.
Si je comprends bien, ce sont bien ces deux lignes qui chargent la lib ?
Code :
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
  read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200\30\2\0\0\0\0\0"..., 832) = 832

Et quand le nouveau programme est fait, peut-être encore un petit coup de strace pour voir les différences ?

Sinon, bon travail Smile
+1 (0) -1 (0) Répondre
28-06-2012, 13h42
Message : #11
ark Hors ligne
Psyckomodo!
*****



Messages : 1,033
Sujets : 48
Points: 317
Inscription : Sep 2011
RE: Introduction a LD_PRELOAD
Merci Spin pour ces remarques, j'en ai édité une partie, mais j'ai pas trop le temps la tout de suite, donc je continuerais plus tard.
Si tu vois d'autres points a corriger, n’hésite pas ! Smile
+1 (0) -1 (0) Répondre


Sujets apparemment similaires…
Sujet Auteur Réponses Affichages Dernier message
  [Tutoriel] Cacher un service derrière un autre avec LD_PRELOAD b0fh 3 372 30-11-2016, 00h06
Dernier message: ZeR0-@bSoLu

Atteindre :


Utilisateur(s) parcourant ce sujet : 2 visiteur(s)
N-PN
Accueil | Challenges | Tutoriels | Téléchargements | Forum | Retourner en haut