¿Por qué y cómo se pueden ejecutar algunas bibliotecas compartidas, como si fueran ejecutables?

56

En sistemas Linux de 32 bits, invocar esto

$ /lib/libc.so.6

y en sistemas de 64 bits esto

$ /lib/x86_64-linux-gnu/libc.so.6

en un shell, proporciona una salida como esta:

GNU C Library stable release version 2.10.1, by Roland McGrath et al.
Copyright (C) 2009 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.
Compiled by GNU CC version 4.4.0 20090506 (Red Hat 4.4.0-4).
Compiled on a Linux >>2.6.18-128.4.1.el5<< system on 2009-08-19.
Available extensions:
    The C stubs add-on version 2.1.2.
    crypt add-on version 2.1 by Michael Glad and others
    GNU Libidn by Simon Josefsson
    Native POSIX Threads Library by Ulrich Drepper et al
    BIND-8.2.3-T5B
    RT using linux kernel aio
For bug reporting instructions, please see:
<http://www.gnu.org/software/libc/bugs.html>.

¿Por qué y cómo sucede esto, y cómo es posible hacer lo mismo en otras bibliotecas compartidas?

Miré /usr/libpara encontrar ejecutables, y encontré /usr/lib/libvlc.so.5.5.0. Ejecutarlo condujo a una falla de segmentación . : - /

Ho1
fuente
Además de todas las siguientes respuestas, mi recuerdo es que si configura el bit x en una biblioteca compartida, era (tal vez aún es posible) cargarlo desde un ejecutable incluso con el bit r despejado. Alguna vez se consideró una buena práctica de seguridad desterrar r bit del mundo en los archivos ejecutables del sistema y las bibliotecas. Debido al código abierto generalizado, esto solo se aplica realmente al directorio anónimo ftp / bin / ls. Para mí, dejar el conjunto de bits x parece un rostro de esa vieja práctica.
Joshua

Respuestas:

53

Esa biblioteca tiene una main()función o punto de entrada equivalente, y se compiló de tal manera que sea útil como un objeto ejecutable y como un objeto compartido.

Aquí hay una sugerencia sobre cómo hacer esto, aunque no funciona para mí.

Aquí hay otro en una respuesta a una pregunta similar sobre SO , que descaradamente plagiaré, retocaré y agregaré un poco de explicación.

Primero, fuente de nuestra biblioteca de ejemplo test.c:

#include <stdio.h>                  

void sayHello (char *tag) {         
    printf("%s: Hello!\n", tag);    
}                                   

int main (int argc, char *argv[]) { 
    sayHello(argv[0]);              
    return 0;                       
}                   

Compila eso:

gcc -fPIC -pie -o libtest.so test.c -Wl,-E

Aquí, estamos compilando una biblioteca compartida ( -fPIC), pero le decimos al vinculador que es un ejecutable regular ( -pie) y que haga que su tabla de símbolos sea exportable ( -Wl,-E), de modo que pueda vincularse de manera útil.

Y, aunque filedirá que es un objeto compartido, funciona como un ejecutable:

> ./libtest.so 
./libtest.so: Hello!

Ahora necesitamos ver si realmente se puede vincular dinámicamente. Un programa de ejemplo program.c:

#include <stdio.h>

extern void sayHello (char*);

int main (int argc, char *argv[]) {
    puts("Test program.");
    sayHello(argv[0]);
    return 0;
}

El uso externnos ahorra tener que crear un encabezado. Ahora compila eso:

gcc program.c -L. -ltest

Antes de poder ejecutarlo, debemos agregar la ruta del libtest.socargador dinámico:

export LD_LIBRARY_PATH=./

Ahora:

> ./a.out
Test program.
./a.out: Hello!

Y ldd a.outmostrará el enlace a libtest.so.

Tenga en cuenta que dudo que así sea cómo se compila realmente glibc, ya que probablemente no sea tan portátil como glibc en sí (ver man gcccon respecto a los interruptores -fPICy -pie), pero demuestra el mecanismo básico. Para los detalles reales, tendrías que mirar el archivo fuente MAKE.

encerrada dorada
fuente
1
Gran respuesta, gracias! :-) Traté de usar nmen la biblioteca compartida, pero no era una versión de depuración. Entonces, ¿por qué libvlcy otros se estrellan?
Ho1
1
Como la mayoría de las bibliotecas compartidas no están destinadas a ser ejecutables, GNU libces una excepción.
Ricitos
Encontré otros dos: ldy libpthread.
Ho1
@ Ho1 ld.soes especial en otros aspectos. Hasta cierto punto, es más un ejecutable real que un ejecutable vinculado dinámicamente normal.
Random832
1
Las opciones anteriores, aunque crean la biblioteca ejecutable-compartida, pero están incompletas en el sentido de que señalan el error, cuando algún ejecutable intenta vincularse a esto. Referencia de muestra detallada agregada aquí: unix.stackexchange.com/a/479334/152034
parasrish el
21

Vamos a buscar una respuesta en el repositorio glibc aleatorio en github. Esta versión proporciona un "banner" en el archivo version.c.

En el mismo archivo hay algunos puntos interesantes: __libc_print_versionla función que proporciona la impresión en el mismo texto y símbolo __libc_main (void)que se documenta como un punto de entrada. Entonces se llama a este símbolo cuando se ejecuta la biblioteca.

Entonces, ¿cómo sabe el enlazador / compilador que esta es exactamente la función del punto de entrada?

Vamos a sumergirnos en el archivo MAKE . En las banderas de enlace hay una bandera interesante:

# Give libc.so an entry point and make it directly runnable itself.
LDFLAGS-c.so += -e __libc_main

Entonces, este es el indicador de enlace para establecer el punto de entrada en la biblioteca. Al crear una biblioteca, puede proporcionar un -e function_namecomportamiento ejecutable para crear enlaces. ¿Qué es lo que realmente hace? Veamos el manual (algo anticuado pero aún válido) :

El lenguaje de comando del vinculador incluye un comando específicamente para definir la primera instrucción ejecutable en un archivo de salida (su punto de entrada). Su argumento es un nombre de símbolo:

ENTRADA (símbolo)

Al igual que las asignaciones de símbolos, el comando ENTRADA puede colocarse como un comando independiente en el archivo de comandos o entre las definiciones de sección dentro del comando SECCIONES, lo que sea más conveniente para su diseño.

ENTRY es solo una de varias formas de elegir el punto de entrada. Puede indicarlo de cualquiera de las siguientes maneras (se muestra en orden descendente de prioridad: los métodos que están más arriba en la lista anulan los métodos que están más abajo).

the `-e' entry command-line option;
the ENTRY(symbol) command in a linker control script;
the value of the symbol start, if present;
the address of the first byte of the .text section, if present;
The address 0. 

Por ejemplo, puede usar estas reglas para generar un punto de entrada con una instrucción de asignación: si no se define un inicio de símbolo dentro de sus archivos de entrada, simplemente puede definirlo, asignándole un valor apropiado ---

inicio = 0x2020;

El ejemplo muestra una dirección absoluta, pero puede usar cualquier expresión. Por ejemplo, si sus archivos de objetos de entrada usan alguna otra convención de nombre de símbolo para el punto de entrada, simplemente puede asignar el valor de cualquier símbolo que contenga la dirección de inicio para comenzar:

inicio = otro_símbolo;

(la documentación actual se puede encontrar aquí )

Realmente ldlinker crea un ejecutable con función de punto de entrada si le proporciona la opción de línea de comando -e(la solución más práctica), proporciona un símbolo de función starto inyecta una dirección de símbolo en el ensamblador.

Sin embargo, tenga en cuenta que claramente no se garantiza que funcione con otros vinculadores (no sé si llvm's lld tiene la misma bandera). Por qué esto debería ser útil para fines distintos de proporcionar información sobre ese archivo, no lo sé.

IBr
fuente
1
Si fuera Python, proporcionaría pruebas unitarias.
Erik Aronesty