¿Trabajas en una empresa de ciberseguridad? ¿Quieres ver tu empresa anunciada en HackTricks? ¿O quieres tener acceso a la última versión del PEASS o descargar HackTricks en PDF? ¡Consulta los PLANES DE SUSCRIPCIÓN!
El programa anterior tiene 9 encabezados de programa, luego, el mapeo de segmentos indica en qué encabezado de programa (de 00 a 08) se encuentra cada sección.
PHDR - Encabezado de Programa
Contiene las tablas de encabezados de programa y los metadatos en sí.
INTERP
Indica la ruta del cargador a utilizar para cargar el binario en memoria.
LOAD
Estos encabezados se utilizan para indicar cómo cargar un binario en memoria.
Cada encabezado LOAD indica una región de memoria (tamaño, permisos y alineación) e indica los bytes del binario ELF a copiar allí.
Por ejemplo, el segundo tiene un tamaño de 0x1190, debería estar ubicado en 0x1fc48 con permisos de lectura y escritura y se llenará con 0x528 desde el desplazamiento 0xfc48 (no llena todo el espacio reservado). Esta memoria contendrá las secciones .init_array .fini_array .dynamic .got .data .bss.
DYNAMIC
Este encabezado ayuda a vincular programas con sus dependencias de bibliotecas y aplicar reubicaciones. Ver la sección .dynamic.
NOTE
Almacena información de metadatos del proveedor sobre el binario.
GNU_EH_FRAME
Define la ubicación de las tablas de desenrollado de la pila, utilizadas por depuradores y funciones de manejo de excepciones de C++ en tiempo de ejecución.
GNU_STACK
Contiene la configuración de la defensa de prevención de ejecución de pila. Si está habilitado, el binario no podrá ejecutar código desde la pila.
GNU_RELRO
Indica la configuración RELRO (Relocation Read-Only) del binario. Esta protección marcará como de solo lectura ciertas secciones de la memoria (como el GOT o las tablas init y fini) después de que el programa se haya cargado y antes de que comience a ejecutarse.
En el ejemplo anterior, se copian 0x3b8 bytes en 0x1fc48 como solo lectura afectando a las secciones .init_array .fini_array .dynamic .got .data .bss.
Tenga en cuenta que RELRO puede ser parcial o completo, la versión parcial no protege la sección .plt.got, que se utiliza para enlace perezoso y necesita que este espacio de memoria tenga permisos de escritura para escribir la dirección de las bibliotecas la primera vez que se busca su ubicación.
TLS
Define una tabla de entradas TLS, que almacena información sobre variables locales de subprocesos.
Encabezados de Sección
Los encabezados de sección ofrecen una vista más detallada del binario ELF.
Tabla de cadenas: Contiene todas las cadenas necesarias para el archivo ELF (pero no las que realmente usa el programa). Por ejemplo, contiene nombres de secciones como .text o .data. Y si .text está en el desplazamiento 45 en la tabla de cadenas, usará el número 45 en el campo nombre.
Para encontrar dónde está la tabla de cadenas, el ELF contiene un puntero a la tabla de cadenas.
Tabla de símbolos: Contiene información sobre los símbolos como el nombre (desplazamiento en la tabla de cadenas), dirección, tamaño y más metadatos sobre el símbolo.
Secciones Principales
.text: Las instrucciones del programa a ejecutar.
.data: Variables globales con un valor definido en el programa.
.bss: Variables globales no inicializadas (o inicializadas a cero). Las variables aquí se inicializan automáticamente a cero, evitando así que se agreguen ceros inútiles al binario.
.rodata: Variables globales constantes (sección de solo lectura).
.tdata y .tbss: Similar a .data y .bss cuando se utilizan variables locales al hilo (__thread_local en C++ o __thread en C).
.dynamic: Ver abajo.
Símbolos
Los símbolos son ubicaciones con nombre en el programa que pueden ser una función, un objeto de datos global, variables locales al hilo...
readelf -s lnstat
Symbol table '.dynsym' contains 49 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000001088 0 SECTION LOCAL DEFAULT 12 .init
2: 0000000000020000 0 SECTION LOCAL DEFAULT 23 .data
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strtok@GLIBC_2.17 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND s[...]@GLIBC_2.17 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strlen@GLIBC_2.17 (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fputs@GLIBC_2.17 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.17 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _[...]@GLIBC_2.34 (3)
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND perror@GLIBC_2.17 (2)
10: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]
11: 0000000000000000 0 FUNC WEAK DEFAULT UND _[...]@GLIBC_2.17 (2)
12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND putc@GLIBC_2.17 (2)
[...]
Cada entrada de símbolo contiene:
Nombre
Atributos de enlace (débil, local o global): Un símbolo local solo puede ser accedido por el programa en sí, mientras que los símbolos globales se comparten fuera del programa. Un objeto débil es, por ejemplo, una función que puede ser reemplazada por otra diferente.
Tipo: NOTYPE (tipo no especificado), OBJECT (variable de datos global), FUNC (función), SECTION (sección), FILE (archivo de código fuente para depuradores), TLS (variable local al hilo), GNU_IFUNC (función indirecta para reubicación)
El directorio NEEDED indica que el programa necesita cargar la biblioteca mencionada para poder continuar. El directorio NEEDED se completa una vez que la biblioteca compartida está completamente operativa y lista para su uso.
Relocalizaciones
El cargador también debe reubicar las dependencias después de haberlas cargado. Estas reubicaciones se indican en la tabla de reubicación en formatos REL o RELA y el número de reubicaciones se da en las secciones dinámicas RELSZ o RELASZ.
Si el programa se carga en un lugar diferente a la dirección preferida (generalmente 0x400000) porque la dirección ya está en uso o debido a ASLR u otra razón, una relocalización estática corrige los punteros que tenían valores esperando que el binario se cargara en la dirección preferida.
Por ejemplo, cualquier sección de tipo R_AARCH64_RELATIV debería haber modificado la dirección en el sesgo de relocalización más el valor del sumando.
Relocalizaciones Dinámicas y GOT
La relocalización también podría hacer referencia a un símbolo externo (como una función de una dependencia). Por ejemplo, la función malloc de libC. Entonces, cuando el cargador carga libC en una dirección, verifica dónde se carga la función malloc, escribirá esta dirección en la tabla GOT (Global Offset Table) (indicada en la tabla de relocalización) donde se debe especificar la dirección de malloc.
Tabla de Enlace de Procedimientos
La sección PLT permite realizar enlace perezoso, lo que significa que la resolución de la ubicación de una función se realizará la primera vez que se acceda a ella.
Entonces, cuando un programa llama a malloc, en realidad llama a la ubicación correspondiente de malloc en la PLT (malloc@plt). La primera vez que se llama, resuelve la dirección de malloc y la almacena para que la próxima vez que se llame a malloc, se utilice esa dirección en lugar del código PLT.
Inicialización del Programa
Después de que el programa se ha cargado, es hora de que se ejecute. Sin embargo, el primer código que se ejecuta no siempre es la función main. Esto se debe a que, por ejemplo, en C++ si una variable global es un objeto de una clase, este objeto debe ser inicializadoantes de que se ejecute main, como en:
Ten en cuenta que estas variables globales se encuentran en .data o .bss, pero en las listas __CTOR_LIST__ y __DTOR_LIST__ se almacenan los objetos a inicializar y destruir para hacer un seguimiento de ellos.
Desde el código C es posible obtener el mismo resultado utilizando las extensiones de GNU:
__attributte__((constructor)) //Add a constructor to execute before__attributte__((destructor)) //Add to the destructor list
Desde la perspectiva de un compilador, para ejecutar estas acciones antes y después de que se ejecute la función main, es posible crear una función init y una función fini que serían referenciadas en la sección dinámica como INIT y FIN y se colocarían en las secciones init y fini del ELF.
La otra opción, como se mencionó, es hacer referencia a las listas __CTOR_LIST__ y __DTOR_LIST__ en las entradas INIT_ARRAY y FINI_ARRAY de la sección dinámica, y la longitud de estas se indica por INIT_ARRAYSZ y FINI_ARRAYSZ. Cada entrada es un puntero a función que se llamará sin argumentos.
Además, también es posible tener un PREINIT_ARRAY con punteros que se ejecutarán antes de los punteros de INIT_ARRAY.
Orden de Inicialización
El programa se carga en memoria, las variables globales estáticas se inicializan en .data y las no inicializadas se ponen a cero en .bss.
Se inicializan todas las dependencias del programa o bibliotecas y se ejecuta el enlace dinámico.
Se ejecutan las funciones de PREINIT_ARRAY.
Se ejecutan las funciones de INIT_ARRAY.
Si hay una entrada de INIT, se llama.
Si es una biblioteca, dlopen termina aquí; si es un programa, es hora de llamar al punto de entrada real (función main).
Almacenamiento Local en Hilos (TLS)
Se definen utilizando la palabra clave __thread_local en C++ o la extensión GNU __thread.
Cada hilo mantendrá una ubicación única para esta variable, por lo que solo el hilo puede acceder a su variable.
Cuando se utiliza esto, se utilizan las secciones .tdata y .tbss en el ELF. Que son como .data (inicializado) y .bss (no inicializado) pero para TLS.
Cada variable tendrá una entrada en el encabezado TLS especificando el tamaño y el desplazamiento TLS, que es el desplazamiento que utilizará en el área de datos local del hilo.
El __TLS_MODULE_BASE es un símbolo utilizado para hacer referencia a la dirección base del almacenamiento local en hilos y apunta al área en memoria que contiene todos los datos locales del hilo de un módulo.