Protección de ejecutables: PIE

PIE o 'Position Independent Executables' son programas ejecutables binarios hechos enteramente de código independiente de posición. Esta protección de los ejecutables viene a complementar la que vimos en un post anterior sobre ASLR (Address Space Layout Randomization) que como vimos es una característica del kernel que una vez activada aleatorizaba las zonas de memoria correspondientes a las bibliotecas compartidas, stack, VDSO y head.

Para que una aplicación pueda usar el código independiente de posición PIE se utilizan los flags -fpie -pie (compilado y linkado) en GCC, opciones que se asignan actualmente de forma automática.

La finalidad de crear binarios PIE es poder crear ejecutables ELF con los mismos atributos que un objeto de biblioteca compartida: código independiente de la posición (PIC) y una dirección base de 0x0 para que el kernel pueda reubicar el binario en tiempo de ejecución.

Las únicas diferencias entre una biblioteca compartida regular y un ejecutable PIE son que el código de inicialización y que los ejecutables deben tener un segmento PT_INTERP para describir la ruta al enlazador dinámico.

Un ejecutable no PIE (compilado con -no-pie) tiene el tipo de archivo ELF ET_EXEC:

$ readelf -h no-PIE
Encabezado ELF:
Mágico: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Clase: ELF64
Datos: complemento a 2, little endian
Versión: 1 (current)
OS/ABI: UNIX - System V
Versión ABI: 0
Tipo: EXEC (Fichero ejecutable)
Máquina: Advanced Micro Devices X86-64
Versión: 0x1
Dirección del punto de entrada: 0x400590
Inicio de encabezados de programa: 64 (bytes en el fichero)
Inicio de encabezados de sección: 9000 (bytes en el fichero)
Opciones: 0x0
Tamaño de este encabezado: 64 (bytes)
Tamaño de encabezados de programa: 56 (bytes)
Número de encabezados de programa: 9
Tamaño de encabezados de sección: 64 (bytes)
Número de encabezados de sección: 35
Índice de tabla de cadenas de sección de encabezado: 34

Mientras que un ejecutable PIE tiene un tipo de archivo ET_DYN, al igual que las bibliotecas compartidas:

$ readelf -h PIE
Encabezado ELF:
Mágico: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Clase: ELF64
Datos: complemento a 2, little endian
Versión: 1 (current)
OS/ABI: UNIX - System V
Versión ABI: 0
Tipo: DYN (Fichero objeto compartido)
Máquina: Advanced Micro Devices X86-64
Versión: 0x1
Dirección del punto de entrada: 0x6d0
Inicio de encabezados de programa: 64 (bytes en el fichero)
Inicio de encabezados de sección: 9008 (bytes en el fichero)
Opciones: 0x0
Tamaño de este encabezado: 64 (bytes)
Tamaño de encabezados de programa: 56 (bytes)
Número de encabezados de programa: 9
Tamaño de encabezados de sección: 64 (bytes)
Número de encabezados de sección: 34
Índice de tabla de cadenas de sección de encabezado: 33

Los ejecutables PIE utilizan el modo de direccionamiento relativo de IP para evitar las referencias de codificación rígida a las direcciones absolutas. Un programa que es un ELF ET_DYN y tiene una dirección base de 0x0 se puede reubicar aleatoriamente en una dirección base diferente cada vez que se ejecuta.

Vamos a utilizar el mismo programa que en la entrada sobre ASLR:

/*Protección:ASLR-PIE */ 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <sys/auxv.h> 
 
char *global_var = "comunidadlibre"; 
 
int main(){ 
    char *heap_var = malloc(100); 
    char *stack_var[] = {"comunidadlibre"}; 
 
    printf("[Heap] dirección: %p\n", heap_var); 
    printf("[Stack] dirección: %p\n", stack_var); 
    printf("[libc] dirección: %p\n", (void*)exit); 
    printf("[.data] dirección: %p\n", global_var); 
    printf("[vDSO] dirección: 0x%08lx\n", getauxval(AT_SYSINFO_EHDR)); 
 
    getchar(); 
 
 
    free(heap_var); 
    exit(EXIT_SUCCESS); 
} 

Tenemos que compilar el programa, esta vez utilizando la protección PIE (que se asigna por defecto actualmente):

$ gcc -Wall -g -fpie -pie PIE.c -o PIE

Si ejecutamos ahora el programa varias veces podemos ver las asignaciones dinámicas de memoria de las diferentes zonas de memoria:

david@u1804libre:~$ ./PIE 
[Heap] dirección: 0x55aedb67c260
[Stack] dirección: 0x7fffaacb84d0
[libc] dirección: 0x7f046659c120
[.data] dirección: 0x55aeda8c7934
[vDSO] dirección: 0x7fffaadb0000

david@u1804libre:~$ ./PIE
[Heap] dirección: 0x560a0cc2c260
[Stack] dirección: 0x7fff8b3ef400
[libc] dirección: 0x7f2289807120
[.data] dirección: 0x560a0b5e6934
[vDSO] dirección: 0x7fff8b3f6000

Como podemos comprobar .data tiene ahora una dirección de memoria diferente en cada ejecución.

PIE también asigna al azar la ubicación del GOT 'global offset table', lo que hace que sobrescribir GOT sea más difícil de lograr.

El uso de las tablas PLT y GOT será el asunto de una próxima entrada.

Modificado por última vez enViernes, 04 Septiembre 2020 19:10
(0 votos)
Etiquetado como :

Deja un comentario

Asegúrese de introducir toda la información requerida, indicada por un asterisco (*). No se permite código HTML.