Introduction a LD_PRELOAD
|
23-06-2012, 13h43
(Modification du message : 08-12-2012, 17h21 par ark.)
Message : #1
|
|
ark
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 :
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) 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 ? 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 :
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 :
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 code final: Code C :
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 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... Voila voila, je vous laisse imaginer tout ce que l'ont peux faire avec ca ! Questions ? Commentaires ? Critiques ? Je prends tout ! |
|
23-06-2012, 14h42
(Modification du message : 23-06-2012, 14h59 par spin.)
Message : #2
|
|
spin
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 P.-S. : programmer en assembleur avec les syscalls directement, y a que ça de vrai de toute façon :p |
|
23-06-2012, 15h16
Message : #3
|
|
ark
Psyckomodo! Messages : 1,033 Sujets : 48 Points: 317 Inscription : Sep 2011 |
RE: Introduction a LD_PRELOAD
Merci Spin pour le commentaire 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) |
|
23-06-2012, 16h35
Message : #4
|
|
supersnail
Éleveur d'ornithorynques Messages : 1,614 Sujets : 72 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 Sinon, essaie de soigner un peu l'ortheaugrafe, parce qu'il y a quelques fautes qui piquent un peu les yeux
Mon blog
Code : push esp ; dec eax ; inc ebp ; and [edi+0x41],al ; dec ebp ; inc ebp "VIM est merveilleux" © supersnail |
|
23-06-2012, 17h32
Message : #5
|
|
ark
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 |
|
26-06-2012, 17h39
(Modification du message : 26-06-2012, 17h47 par Dobry.)
Message : #6
|
|
Dobry
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: 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: Code : push ebp ; Sauvegarde d'EBP 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 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: (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.
|
|
26-06-2012, 18h05
Message : #7
|
|
spin
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. |
|
26-06-2012, 18h50
Message : #8
|
|
Dobry
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.
|
|
26-06-2012, 19h17
Message : #9
|
|
spin
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 |
|
28-06-2012, 12h36
Message : #10
|
|
spin
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
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 !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 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 Et quand le nouveau programme est fait, peut-être encore un petit coup de strace pour voir les différences ? Sinon, bon travail |
|
28-06-2012, 13h42
Message : #11
|
|
ark
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 ! |
|
« Sujet précédent | Sujet suivant »
|
Sujets apparemment similaires… | |||||
Sujet | Auteur | Réponses | Affichages | Dernier message | |
[Tutoriel] Cacher un service derrière un autre avec LD_PRELOAD | b0fh | 3 | 2,227 |
30-11-2016, 00h06 Dernier message: ZeR0-@bSoLu |
Utilisateur(s) parcourant ce sujet : 7 visiteur(s)