Gcc sous Debian crée une bibliothèque et non un exécutable

a marqué ce sujet comme résolu.

Bonjour à tous.

J’ai récemment réinstallé mon Linux (j’avais une distribution LMDE et je suis passé directement sous Debian Stretch).

J’ai un petit soucis avec Gcc : depuis le passage sous Debian, les exécutables créés avec Gcc (que ce sois avec Code::Blocks ou par Makefile), ne sont plus reconnus comme tels, mais comme "Bibliothèque partagée".

Il m’est donc impossible d’en lancer un directement par double-clic depuis le gestionnaire de fichiers. Par contre, depuis un terminal, ça marche.

De même, si je crée un lanceur, ça fonctionne sans problème avec le double clic (sur le lanceur).

J’ai même fait le test avec un code des plus basic (un simple Hello World compilé en ligne de commande avec g++ -o TestExe main.cpp) et le résultat est le même.

Pour info :

1
2
3
g++ --version
g++ (Debian 6.3.0-18) 6.3.0 20170516
Copyright (C) 2016 Free Software Foundation, Inc.

Est-ce que quelqu’un a déjà eut ce problème, et surtout comment le résoudre ?

Merci d’avance.

Xav’

J’ai un petit soucis avec Gcc : depuis le passage sous Debian, les exécutables créés avec Gcc (que ce sois avec Code::Blocks ou par Makefile), ne sont plus reconnus comme tels, mais comme "Bibliothèque partagée".

Il m’est donc impossible d’en lancer un directement par double-clic depuis le gestionnaire de fichiers. Par contre, depuis un terminal, ça marche.

Xaviou

Salut,

Il semblerait que gcc n’ait donc rien à voir avec ton problème, cela vient de la configuration de ton environnement de bureau / explorateur de fichiers.

Merci à tous les deux pour vos réponses.

Il semblerait que gcc n’ait donc rien à voir avec ton problème, cela vient de la configuration de ton environnement de bureau / explorateur de fichiers.

entwanne

J’y ai pensé, mais franchement j’en doute.

J’ai remis la main sur un ancien dossier contenant un projet basic en mode console qui était compilé. L’exécutable fait à l’époque est bien reconnu comme "Exécutable".
Malheureusement, je ne peux pas voir ce que donne le double clic car c’est juste un "Hello World" donc je ne vois aucun résultat en mode graphique (mais il marche bien en mode console).
J’ai fait une copie de l’exécutable et je l’ai recompilé (c’était un projet Code::Blocks).
Cette fois, le résultat est une bibliothèque partagée

J’ai aussi l’exemple d’un autre dossier contenant une application avec interface graphique (faite avec wxWidgets) mais l’éxécution ne marche pas (ça vient des paramètres de compilation de wxWidgets que j’utilisait à l’époque : j’obtiens une erreur en mode console concernant une lib manquante et comme un c*on que je suis, j’ai relancé la compilation sans faire une copie du fichier).

Peux-tu donner le résultat de file tonfichierexecutable pour confirmer ce que dit mon VDD ?

unidan
1
2
xavier:GccTest $ file TestExe 
TestExe: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=39a6570b98672277c4a56f62dd5d3afc1795dc19, not stripped

Pendant que j’y était, j’ai fait la même chose sur les deux exéctuables cités précédement (celuis dont j’ai fait la copie et celui que j’ai recompilé) : Pour l’original (celui qui est reconnu comme un exécutable) :

1
2
xavier:bin $ file ConsoleTest-old
ConsoleTest-old: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=2cf2d819b0f6a547a01c60eef8289f849ab297b9, not stripped

Et pour celui que j’ai recompilé :

1
2
xavier:bin $ file ConsoleTest
ConsoleTest: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=468fb7163062c42f62a4185911706373fff0233c, not stripped

Je n’ai pourtant rien touché concernant les options de compilation : j’ai juste recompilé.

C’est à n’y rien comprendre…

Ah, désolé, effectivement le problème vient de la compilation. On dirait que l’option -shared de gcc est activée.

La ligne que tu as donnée est la ligne de compilation exacte que tu utilises ? Tu pourrais nous montrer le résultat d’un alias pour vérifier que tu n’as pas ajouté quelques paramètres par défaut ?

Ah, désolé, effectivement le problème vient de la compilation. On dirait que l’option -shared de gcc est activée.

La ligne que tu as donnée est la ligne de compilation exacte que tu utilises ?

entwanne

Oui, c’est la commande exacte que j’ai utilisé pour faire le test (correspondant au premier fichier sur lequel j’ai lancé la commande file) :

1
g++ -o TestExe main.cpp

Tu pourrais nous montrer le résultat d’un alias pour vérifier que tu n’as pas ajouté quelques paramètres par défaut ?

entwanne
1
2
xavier:GccTest $ alias
alias ls='ls --color=auto'

En fait, si c’était réellement une bibliothèque partagée, le système n’accepterait sans doute pas de l’exécuter comme ça en ligne de commande (enfin, c’est qu’il me semble mais sait-on jamais…).

D’ailleurs je viens de faire le test : j’ai recompilé le fichier en mode shared :

1
g++ -o TestShared -shared -fPIC main.cpp

J’ai été obligé de rajouter -fPIC pour que la compilation aboutisse. J’ai maintenant 2 fichiers reconnus comme "Bibliothèque partagée" dans ce dossier mais le résultat de file ne donne pas le même résultat :

1
2
3
4
xavier:GccTest $ file TestShared 
TestShared: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=353734c55b54555c2f3c406bbe75ffffb252a332, not stripped
xavier:GccTest $ file TestExe 
TestExe: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=39a6570b98672277c4a56f62dd5d3afc1795dc19, not stripped

Et bien entendu, il n’est pas possible d’exécuter le fichier "TestShared" même en mode console : Erreur de segmentation

:'(

Si tu as vraiment besoin de résoudre le problème en vitesse, utilise un autre compilo comme "clang++".

Sinon, j’aurais tenté un g++ -o TestExe main.cpp & ps aux | grep g++ pour voir les arguments de g++.

+0 -0

Si tu as vraiment besoin de résoudre le problème en vitesse, utilise un autre compilo comme "clang++".

ache

Non : pas de contrainte de temps.
Ça fait quelques temps que ça dure et pour les exécutables dont jeme sert régulièrement, j’utilise un lanceur.
Mais c’est surtout une question de principe : j’aimerais bien savoir d’où vient le problème.

Sinon, j’aurais tenté un g++ -o TestExe main.cpp & ps aux | grep g++ pour voir les arguments de g++.

ache
1
2
3
4
xavier:GccTest $ g++ -o TestExe main.cpp & ps aux | grep g++
[1] 10227
xavier   10227  0.0  0.0   9980   924 pts/0    S    15:22   0:00 g++ -o TestExe main.cpp
xavier   10229  0.0  0.0  12784   980 pts/0    S+   15:22   0:00 grep g++

Apparemment y’a rien de plus que ce que je lui donne.

J’ai pas vraiment tout compris le pourquoi du comment, mais avec g++ -o TestExe -no-pie main.cpp j’obtiens bien un exécutable.

Xaviou

Parce que d’une part, tu ne spécifies à aucun moment que tu souhaites avoir un objet partagé lors de l’édition de liens (implicite), d’autre part, l’option -no-pie spécifie que ton binaire sera toujours chargé en mémoire à la même adresse de référence.

"pie" signifie Position Independent Executable. L’intérêt d’utiliser cette option est de pouvoir, à chaque exécution de ton binaire, le charger à des adresses mémoires différentes (exactement comme pour des objets partagés qui ont besoin d’être relocalisés en mémoire en permanence plutôt que de toujours être chargés à une adresse mémoire fixe, ce qui peut entraîner des problèmes de collisions si deux objets partagés doivent être chargés à la même adresse, par exemple).

L’intérêt de faire ça pour un binaire exécutable et pourquoi pas seulement pour les objets partagés ? Eh bien il s’agit d’une mesure d’atténuation (je ne sais pas comment traduire correctement "mitigation") d’attaques informatique de type Return Oriented Programming où tu vas corrompre la mémoire de ton binaire pour exécuter un code arbitraire. Et pour exécuter ce code arbitraire, tu as besoin de savoir à quels adresses mémoires chercher les bonnes informations. Et comme avec l’option PIE les adresses ne sont jamais les mêmes à chaque exécution, ça rend l’exploitation d’une vulnérabilité plus compliqué que prévu.

N’hésite pas à poser des questions si mon explication n’était pas claire.

Plus d’informations ici : https://zestedesavoir.com/articles/1424/decouvrez-lattaque-return-oriented-programming/

Salut,

Parce que d’une part, tu ne spécifies à aucun moment que tu souhaites avoir un objet partagé lors de l’édition de liens (implicite) […]

Ge0

Peux-tu détaillé ce que que tu veux dire par là ?

Sinon, si je ne dis pas de bêtises, il me semble que lorsqu’un code est compilé avec le PIE activé, il ne peut pas être chargé directement en mémoire comme un exécutable classique (édit : je veux dire, en dehors du fait que s’il est compilé dynamiquement, ses dépendances doivent d’abord être chargées). Typiquement, sous GNU/Linux et *BSD (je parle ici avant tout pour OpenBSD), c’est d’abord le programme ld.so qui sera exécuté, qui chargera le programme, adaptera ses adresses, puis lui passera la main. En gros, un programme PIE est une sorte de « bibliothèque » chargée par ld.so.

édit 2 : je n’ai rien dit.

+1 -0

Peux-tu détaillé ce que que tu veux dire par là ?

Taurre

L’option -shared n’est pas spécifiée.

Et je ne sais pas non plus pour ld.so. Mais pour moi ça n’est aucunement différent du chargement en mémoire d’un programme normal, tu auras juste une section de "relocs" en plus pour relocaliser les adresses mémoires de ton binaire dont la position du code est indépendante.

Ca reste un sujet d’étude passionnant…

En tout cas, je ne sais pas si c’est un élément de réponse, mais que j’aie -no-pie ou non, j’obtiens ça dans mon ouput de strace :

1
2
3
4
5
6
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=47555, ...}) = 0
mmap(NULL, 47555, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f8951d29000
close(3)                                = 0
openat(AT_FDCWD, "/usr/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200\20\2\0\0\0\0\0"..., 832) = 832

Il y a un appel à ld.so. Reste à savoir ce qui se fait dans les détails ensuite. Tu vois que le binaire est chargé en mémoire (read) et tu peux imaginer après des opérations de "relocalisation" ou non, ceci en plus de charger les objets partagés communs qui vont bien (libc.so, …).

En tout cas, je ne sais pas si c’est un élément de réponse, mais que j’aie -no-pie ou non, j’obtiens ça dans mon ouput de strace :

1
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
Ge0

Juste pour dire : ld.so.cache, si je ne me trompe pas, est un fichier généré (par ldconfig(8) je crois) servant de cache pour les bibliothèques partagées. ’Fin je suppose que ld.so est appelé avant puisque ce fichier est lu, mais bref.

De mon côté, je soupçonne ld.so de s’en charger parce qu’il m’est impossible de construire un exécutable statique valide avec du PIE (je n’ai testé que sous OpenBSD).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <stdio.h>                                                                                                                     


int
main(void)
{
        int x = 10;
        int y = 20;

        printf("x = %d, y = %d\n", x, y);
        return 0;
}
1
2
3
4
5
6
$ gcc -fno-builtin -O0 -Wl,-static main.c -o x
$ ./x
Segmentation fault (core dumped)                                                                                                       
$ gcc -fno-builtin -O0 -Wl,-static,-nopie main.c -o x
$ ./x
x = 10, y = 20
+0 -0

Tu pourrais nous dire d’où vient précisément le problème avec gdb ?

De mon côté j’essaie de compiler en statique sur mon ArchLinux mais à l’heure où j’écris ces lignes, je ne trouve pas le paquet ou l’astuce qui me permet de le faire…

1
2
3
4
$ gcc -fno-builtin -O0 -Wl,-static test.c -o x                                                                                                                                               
/usr/bin/ld: cannot find -lgcc_s
/usr/bin/ld: cannot find -lgcc_s
collect2: error: ld returned 1 exit status

Tu pourrais nous dire d’où vient précisément le problème avec gdb ?

Ge0

Voici la sortie de gdb.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
(gdb) start
Breakpoint 1 at 0x4c0: file main.c, line 7.
Starting program: /tmp/x 
Breakpoint 1 at 0x1e390b7004c0: file main.c, line 7.

Program received signal SIGSEGV, Segmentation fault.
0x00001e390b70a52b in _libc___cxa_atexit (func=0x12a40, arg=0x0, dso=0x0)
    at /usr/src/lib/libc/stdlib/atexit.c:75
75      /usr/src/lib/libc/stdlib/atexit.c: No such file or directory.
        in /usr/src/lib/libc/stdlib/atexit.c
Current language:  auto; currently minimal

Et de ktrace (équivalent de strace).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
 44696 ktrace   RET   ktrace 0
 44696 ktrace   CALL  execve(0x7f7ffffe9cdf,0x7f7ffffe9c00,0x7f7ffffe9c10)
 44696 ktrace   NAMI  "./x"
 44696 ktrace   ARGS  
        [0] = "./x"
 44696 x        RET   execve 0
 44696 x        CALL  kbind(0,0,0)
 44696 x        RET   kbind 0
 44696 x        CALL  mmap(0,0x1000,0x3<PROT_READ|PROT_WRITE>,0x1002<MAP_PRIVATE|MAP_ANON>,-1,0)
 44696 x        RET   mmap 35191004700672/0x20018b55a000
 44696 x        CALL  getthrid()
 44696 x        RET   getthrid 162629/0x27b45
 44696 x        CALL  __set_tcb(0x20018b55a000)
 44696 x        RET   __set_tcb 0
 44696 x        PSIG  SIGSEGV SIG_DFL code SEGV_MAPERR<1> addr=0x219190 trapno=6
 44696 x        NAMI  "x.core"

De mon côté j’essaie de compiler en statique sur mon ArchLinux mais à l’heure où j’écris ces lignes, je ne trouve pas le paquet ou l’astuce qui me permet de le faire…

1
2
3
4
$ gcc -fno-builtin -O0 -Wl,-static test.c -o x                                                                                                                                               
/usr/bin/ld: cannot find -lgcc_s
/usr/bin/ld: cannot find -lgcc_s
collect2: error: ld returned 1 exit status
Ge0

Il semble que l’option -static doit être passée au compilateur et non à l’éditeur de lien, bizarrement…

1
$ gcc -fno-builtin -O0 -static test.c -o x

Cela étant, la création d’exécutables statiques avec le PIE semble ne pas être une sinécure sous GNU/Linux (note : le message en question date de 2015, cela peut-être changé depuis).

+0 -0

Cela étant, la création d’exécutables statiques avec le PIE semble ne pas être une sinécure sous GNU/Linux (note : le message en question date de 2015, cela peut-être changé depuis).

Taurre

Oui, j’irais même jusqu’à dire que cela revient à se tirer une balle dans le pied.

Comme je l’avais expliqué plus haut, l’option PIE permet, entre autre d’offrir des filtres de sécurité contre des attaques où un code arbitraire utiliserait des adresses mémoires figées à chaque exécution.

Dans un binaire non-PIE, donc la section de code (.code ou .text ? Je ne me souviens plus) exécutable est chargée aux mêmes adresses, plus il y a de code, mieux c’est. Avec un binaire statique, c’est open bar. On remarquera d’ailleurs, dans mon article, que j’ai compilé l’exemple exprès avec l’option -static pour aboutir à une exploitation aisée.

1
gcc -m32 -static -fno-stack-protector -o ropme main.c

Sans cette option, il y aurait beaucoup moins de code dans le binaire final. Le reste du code se trouverait dans des objets partagés… roulement de tambours relocalisables, donc difficilement utilisables par une exploitation de type ROP. :D

Grâce à cette réflexion, je m’en vais rajouter un paragraphe sur mon article pour parler de cette histoire de liaison statique qui, à mon sens, est à proscrire selon les impératifs de sécurité.

J’ai du mal à interpréter les outputs de gdb et de ktrace, sinon. :( Je suis dégoûté de ne pas avoir d’OpenBSD sous la main. Tu sais si un binaire BSD fonctionnerait sous Linux (même format ELF, norme POSIX et tout le tralala) ? Si tel est le cas, tu penses pouvoir m’envoyer le binaire pour que je regarde ?

Grâce à cette réflexion, je m’en vais rajouter un paragraphe sur mon article pour parler de cette histoire de liaison statique qui, à mon sens, est à proscrire selon les impératifs de sécurité.

Ge0

Mmm… Je peux te dire de ce côté que les exécutables de base (par exemple /bin/ls) sous OpenBSD sont compilés en statique avec le PIE, notamment pour permettre à l’administrateur d’employer le système en single user sans avoir besoin d’évetuelles bibliothèques situées sur une autre partition que la partition racine (typiquement /usr/lib).

Je constate à ce sujet que si je compile avec la commande gcc main.c -static -pie -fno-builtin -O0 -o x, le programme fonctionne, mais si je compile avec gcc main.c -Wl,-static,-pie -fno-builtin -O0 -o x, là ça foire… Visiblement le compilateur doit effectuer des transformations avant l’édition des liens.

J’ai du mal à interpréter les outputs de gdb et de ktrace, sinon. :( Je suis dégoûté de ne pas avoir d’OpenBSD sous la main. Tu sais si un binaire BSD fonctionnerait sous Linux (même format ELF, norme POSIX et tout le tralala) ? Si tel est le cas, tu penses pouvoir m’envoyer le binaire pour que je regarde ?

Ge0

Ça peut vite s’arranger, l’installation de base d’OpenBSD fait moins de 400Mo. ^^
Sinon, les exécutables sous OpenBSD sont effectivement au format ELF, mais sont à leurs sauce, ils ne sont donc pas exécutable en tant que tel sous Linux. Cela étant les outils d’analyses habituels (objdump, readelf, etc.) fonctionnent, je peux donc sans problème te fournir un binaire si tu souhaites l’analyser avec ces programmes.

+0 -0

Mmm… Je peux te dire de ce côté que les exécutables de base (par exemple /bin/ls) sous OpenBSD sont compilés en statique avec le PIE, notamment pour permettre à l’administrateur d’employer le système en single user sans avoir besoin d’évetuelles bibliothèques situées sur une autre partition que la partition racine (typiquement /usr/lib).

Taurre

Ça paraît pertinent, en effet.

Sinon, les exécutables sous OpenBSD sont effectivement au format ELF, mais sont à leurs sauce, ils ne sont donc pas exécutable en tant que tel sous Linux. Cela étant les outils d’analyses habituels (objdump, readelf, etc.) fonctionnent, je peux donc sans problème te fournir un binaire si tu souhaites l’analyser avec ces programmes.

Taurre

Si ça ne t’ennuie pas, ça serait cool !

Sinon, les exécutables sous OpenBSD sont effectivement au format ELF, mais sont à leurs sauce, ils ne sont donc pas exécutable en tant que tel sous Linux.

Question peut-être bête, mais les numéros des syscalls ne diffèrent-ils pas complètement entre OpenBSD et Linux ?

+0 -0
Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte