Prenons, le cas du programme élémentaire suivant :
#include <stdio.h>
#include <stdlib.h>
int i;
int main() {
int j;
int *p = malloc(sizeof(int));
printf("%18p static\n",&i);
printf("%18p code\n",&main);
printf("%18p stack\n",&j);
printf("%18p heap\n",p);
printf("%18p libs\n",&printf);
printf("%18p libs\n",&printf);
free(p);
return 0;
}
L'idée est d'essayer d'observer quelles parties de l'espace d'adressage sont réservées aux variables statiques, au code, à la pile et au tas.
Ok let's go.
<nivose-2-[~/tmp]> uname -a
SunOS nivose 5.10 Generic_144488-01 sun4u sparc SUNW,SPARC-Enterprise
<nivose-3-[~/tmp]> gcc --version
gcc (GCC) 3.4.3 (csl-sol210-3_4-branch+sol_rpath)
Copyright (C) 2004 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
<nivose-4-[~/tmp]> gcc -Wall -o vir vir.c
<nivose-9-[~/tmp]> nm -tx vir
vir:
[Index] Value Size Type Bind Other Shndx Name
...
[76] |0x00020a60|0x00000004|OBJT |GLOB |0 |22 |i
[68] |0x00010700|0x000000a8|FUNC |GLOB |0 |9 |main
[58] |0x00020938|0x00000000|FUNC |GLOB |0 |UNDEF |printf
...
<nivose-12-[~/tmp]> ./vir
20a60 static
10700 code
ffbffa4c stack
20a78 heap
20938 libs
<nivose-13-[~/tmp]>
Parfait! Sur le SUN que j'ai sous la main tout semble aller pour le mieux. Dans l'ordre des adresses croissantes, mon espace d'adresse contient : le code, immédiatement suivi de la zone static, des fonctions de bibliothèques puis du tas, enfin la pile démarre d'une adresse en fin d'espace (on rappelle ici que les piles descendent en grandissant...). C'est une situation tout à fait conforme à ce qui est généralement décrit. Au passage, on remarque que très vraisemblablement l'espace d'adressage est 32 bits (bon c'est sûrement une combinaison subtile de vieilleries - machine, os, compilo, etc).
La curiosité nous tient, alors on regarde sur une autre machine :
<lucien-5-[~/tmp]> uname -a
Linux lucien 3.2.0-4-amd64 #1 SMP Debian 3.2.51-1 x86_64 GNU/Linux
<lucien-6-[~/tmp]> gcc --version
gcc (Debian 4.7.2-5) 4.7.2
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
<lucien-7-[~/tmp]> gcc -Wall -o vir vir.c
<lucien-8-[~/tmp]> nm vir
0000000000600a4c B i
00000000004005ac T main
U printf@@GLIBC_2.2.5
<lucien-9-[~/tmp]> ./vir
0x600a4c static
0x4005ac code
0x7fff207bb8e4 stack
0x10b8010 heap
0x400470 libs
<lucien-11-[~/tmp]>
La vie est délicieuse, c'est légèrement différent mais conforme à l'esprit de la chose. Ici dans l'ordre on a les libs, le code, la zone statique, le tas et en fin d'espace la pile. Donc les trucs statiques d'abord, suivi par les machins dynamiques pour qu'il puissent s'étendre l'un d'un côté, l'autre de l'autre. Ok ici, l'adresse de la pile est vraiment grande, on doit être dans un espace d'adressage à 64 bits.
Allons voir encore ailleurs...
[Ciboulette:~/examples] yunes% uname -a
Darwin Ciboulette.local 13.1.0 Darwin Kernel Version 13.1.0: Thu Jan 16 19:40:37 PST 2014; root:xnu-2422.90.20~2/RELEASE_X86_64 x86_64
[Ciboulette:~/examples] yunes% gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 5.0 (clang-500.2.79) (based on LLVM 3.3svn)
Target: x86_64-apple-darwin13.1.0
Thread model: posix
[Ciboulette:~/examples] yunes% gcc -Wall -o vir vir.c
[Ciboulette:~/examples] yunes% nm vir
0000000100000000 T __mh_execute_header
U _free
0000000100001030 S _i
0000000100000e70 T _main
U _malloc
U _printf
U dyld_stub_binder
[Ciboulette:~/examples] yunes% ./vir
0x10a102030 static
0x10a101e70 code
0x7fff55afe958 stack
0x7fd7fac03a40 heap
0x7fff8c0218a8 libs
[Ciboulette:~/examples] yunes%
Oh my god! J'ai bien quelque chose de cohérent puisque l'espace de largeur 64 bits contient visiblement et dans l'ordre : le code, la zone statique, puis loin ailleurs le tas, bien plus loin encore la pile et derrière la pile les libs. Ce qui perturbe est que les adresses virtuelles déterminées à la compilation, par exemple 100000103 pour la variable globale i a à l'exécution la valeur 10a102030!!! Bizarre ce truc (la même chose peut-être observée pour les autres adresses). Les informaticiens ont parfois des pensées étranges (le psychanalyste dirait magiques), me voilà saisit d'une idée (magique donc : « bon la machine n'a bien compris, je vais lui relancer le programme et maintenant elle va faire les choses bien, hein cocotte ? »). Alors soit :
[Ciboulette:~/examples] yunes% ./vir
0x10d7e8030 static
0x10d7e7e70 code
0x7fff52418958 stack
0x7fb8c0c03a40 heap
0x7fff8c0218a8 libs
[Ciboulette:~/examples] yunes%
Oh boy! Les valeurs ne sont pas les mêmes d'une exécution à l'autre! (Une pensée sombre traverse l'esprit perturbé de l'expérimentateur : il doit retourner refaire ses études depuis le départ). Troisième tentative (avant d'aller s'inscrire en première année) :
[Ciboulette:~/examples] yunes% ./vir
0x106ee3030 static
0x106ee2e70 code
0x7fff58d1d958 stack
0x7fc64b403a40 heap
0x7fff8c0218a8 libs
[Ciboulette:~/examples] yunes%
Bon, il y a tout de même un motif, les bits de poids faibles semblent bien correspondre à ce qu'indique la table des symboles de l'exécutable... Alors, c'est quoi ce truc ?
Essayons de déboguer :
[Ciboulette:~/examples] yunes% gdb vir
GNU gdb 6.3.50-20050815 (Apple version gdb-1824) (Wed Feb 6 22:51:23 UTC 2013)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "x86_64-apple-darwin"...Reading symbols for shared libraries .. done
(gdb) run
Starting program: /Users/yunes/examples/vir
...
............ done
0x100001030 static
0x100000e70 code
0x7fff5fbff8b8 stack
0x100103aa0 heap
0x7fff8c0218a8 libs
Program exited normally.
(gdb)
Oh dear! Avec un débogueur j'ai les bonnes adresses! Qu'est ce que j'ai fait, qui m'en veut autant ?
La réponse est en fait simple. Le mécanisme est l'ASLR! Soit Address Space Layout Randomization. Koikidilui ? Le mécanisme est une protection (faible) destinée à perturber l'écriture de virus ou d'attaques. Les adresses étant virtuelle, la machine est donc libre, au chargement du code (i.e. lors de l'exec()) de reloger les variables comme bon lui semble! En pratique, le déplacement consiste simplement à perturber aléatoirement certains bits de l'adresse virtuelle de la table des symboles.
Cette fonctionnalité n'existe pas sur tous les systèmes, mais semble être désormais assez répandue. D'autre part, elle peut-être désactivée à divers niveaux. Nous renvoyons le lecteur à la documentation afférente à son système pour savoir : (1) si l'ASLR est présent, (2) quels sont les espaces qui peuvent être relogés ? (3) déterminer si la fonctionnalité est active, etc.
Pour mon MacOSX, je peux :
- soit utiliser l'option --no-pie à la compilation qui a pour effet (entre autres) de désactiver l'ASLR
- soit modifier le champ MH_PIE dans l'entête Mach-O de l'exécutable. Il existe un script Python sur Internet pour le faire : ici
- peut-être une autre technique exotique... Genre un appel à une fonction pour désactiver la fonctionnalité avec un flag exotique lui aussi avant un exec() (_POSIX_SPAWN_DISABLE_ASLR par exemple)
DjiBee
PS: C'est à Jean-Marie Rifflet que je dois cette expérimentation. Le symptôme qu'il avait observé nous avait d'abord plongé tous les deux dans un drôle d'état (computer science?) ou abîme de perplexité (psychanalyse?)...
PS: C'est à Jean-Marie Rifflet que je dois cette expérimentation. Le symptôme qu'il avait observé nous avait d'abord plongé tous les deux dans un drôle d'état (computer science?) ou abîme de perplexité (psychanalyse?)...