Protección de ejecutables: NX

Una de las funcionalidades que se han ido añadiendo para la creación de ejecutables más seguros, con el fin de mitigar la explotación de vulnerabilidades inyectando shellcodes en los mismos, es NX. NX es la abreviación de no ejecutar, o segmento no ejecutable. Lo que hace es que cuando se carga la aplicación en la memoria no permite que en determinados segmentos se pueda ejecutar código, es decir, marca estos segmentos como no ejecutables y si se intenta ejecutar código desde ahí se produce una excepción.

La idea es que la memoria que permite escribir en ella no debe ejecutar código al igual que la que permite ejecutar código no debe permite escribir en ella. Hace uso de una características hardware (el bit NX - no-execute bit), o en algunos casos por una emulación por software. Podemos ver si esta activada esta característica en nuestra CPU en las propiedades de la cpu:

grep nx /proc/cpuinfo
flags        : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm 3dnowext 3dnow constant_tsc rep_good nopl nonstop_tsc cpuid extd_apicid aperfmperf pni monitor cx16 popcnt lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs skinit wdt hw_pstate vmmcall arat npt lbrv svm_lock nrip_save pausefilter

El kernel de linux soporta el bit NX desde la versión 2.6.8 (de agosto de 2004). Prácticamente todos los sistemas operativos actualmente implementan este tipo de protección. En Window se conoce con el nombre de "Data Execution Prevention" (DEP).

El compilador gcc activa la protección NX por defecto cuando construimos un ejecutable. Si queremos desactivarla tenemos que añadir la opción -z execstack.

Vamos a utilizar un programa sencillo de ejemplo:

/*Protección:NX */ 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
 
void fun1(char *str) { 
    char strfun[256]; 
 
    strcpy(strfun, str); 
    printf("[%p]func1: %s\n", strfun, strfun); 
} 
 
int main(){ 
    int n = 0; 
    char str[500]; 
    char *inheap = (char*)malloc(100); 
 
    printf("Input: "); 
 
    n = scanf("%s", str); 
    if(n>0) { 
        fun1(str); 
        strcpy(inheap, str); 
        printf("[%p]inheap: %s\n", inheap, inheap); 
    } 
 
    free(inheap); 
    exit(EXIT_SUCCESS); 
} 

Vamos a compilar el programa con NX (por defecto se incluye):

gcc -Wall nx.c -o nx

y sin NX:

gcc -Wall -z execstack nx.c -o no-nx

Si comprobamos los ejecutables resultantes con el script basch checksec, que comprueba si están activadas las propiedades de seguridad en un ejecutable, tenemos:

checksec --file nx
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH    Symbols        FORTIFY    Fortified    Fortifiable  FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   70 Symbols    Yes     0     4 nx
checksec --file no-nx
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH    Symbols        FORTIFY    Fortified    Fortifiable  FILE
Full RELRO      Canary found      NX disabled   PIE enabled     No RPATH   No RUNPATH   70 Symbols    Yes     0         4 no-nx

Y podemos verificar en tiempo real como son otorgados los permisos en la memoria cuando ejecutamos nuestra aplicación. Lanzamos la aplicación y desde otro terminal vemos el proceso y examinamos los permisos del área de memoria:

./nx 
Input:

Desde otro terminal:

ps aux|grep nx
david    29827  0.0  0.0   4508   716 pts/0    S+   20:11   0:00 ./nx
david    29851  0.0  0.0  21536  1044 pts/1    S+   20:11   0:00 grep --color=auto nx

cat /proc/29827/maps
560690ef2000-560690ef3000 r-xp 00000000 08:22 113119583                  /home/david/Proyects/protect-shellcode/nx-dep/nx
5606910f2000-5606910f3000 r--p 00000000 08:22 113119583                  /home/david/Proyects/protect-shellcode/nx-dep/nx
5606910f3000-5606910f4000 rw-p 00001000 08:22 113119583                  /home/david/Proyects/protect-shellcode/nx-dep/nx
560691dc8000-560691de9000 rw-p 00000000 00:00 0                          [heap]
7fb092406000-7fb0925ed000 r-xp 00000000 08:22 25694770                   /lib/x86_64-linux-gnu/libc-2.27.so
7fb0925ed000-7fb0927ed000 ---p 001e7000 08:22 25694770                   /lib/x86_64-linux-gnu/libc-2.27.so
7fb0927ed000-7fb0927f1000 r--p 001e7000 08:22 25694770                   /lib/x86_64-linux-gnu/libc-2.27.so
7fb0927f1000-7fb0927f3000 rw-p 001eb000 08:22 25694770                   /lib/x86_64-linux-gnu/libc-2.27.so
7fb0927f3000-7fb0927f7000 rw-p 00000000 00:00 0
7fb0927f7000-7fb09281e000 r-xp 00000000 08:22 25694742                   /lib/x86_64-linux-gnu/ld-2.27.so
7fb0929f3000-7fb0929f5000 rw-p 00000000 00:00 0
7fb092a1e000-7fb092a1f000 r--p 00027000 08:22 25694742                   /lib/x86_64-linux-gnu/ld-2.27.so
7fb092a1f000-7fb092a20000 rw-p 00028000 08:22 25694742                   /lib/x86_64-linux-gnu/ld-2.27.so
7fb092a20000-7fb092a21000 rw-p 00000000 00:00 0
7fff8da4b000-7fff8da6c000 rw-p 00000000 00:00 0                          [stack]
7fff8db7c000-7fff8db7f000 r--p 00000000 00:00 0                          [vvar]
7fff8db7f000-7fff8db81000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

y el mismo proceso para nuestro programa sin NX:

./no-nx
Input:

y desde otro terminal:

cat /proc/29987/maps
5616a92f5000-5616a92f6000 r-xp 00000000 08:22 113116874                  /home/david/Proyects/protect-shellcode/nx-dep/no-nx
5616a94f5000-5616a94f6000 r-xp 00000000 08:22 113116874                  /home/david/Proyects/protect-shellcode/nx-dep/no-nx
5616a94f6000-5616a94f7000 rwxp 00001000 08:22 113116874                  /home/david/Proyects/protect-shellcode/nx-dep/no-nx
5616aaf47000-5616aaf68000 rwxp 00000000 00:00 0                          [heap]
7fa7930e7000-7fa7932ce000 r-xp 00000000 08:22 25694770                   /lib/x86_64-linux-gnu/libc-2.27.so
7fa7932ce000-7fa7934ce000 ---p 001e7000 08:22 25694770                   /lib/x86_64-linux-gnu/libc-2.27.so
7fa7934ce000-7fa7934d2000 r-xp 001e7000 08:22 25694770                   /lib/x86_64-linux-gnu/libc-2.27.so
7fa7934d2000-7fa7934d4000 rwxp 001eb000 08:22 25694770                   /lib/x86_64-linux-gnu/libc-2.27.so
7fa7934d4000-7fa7934d8000 rwxp 00000000 00:00 0
7fa7934d8000-7fa7934ff000 r-xp 00000000 08:22 25694742                   /lib/x86_64-linux-gnu/ld-2.27.so
7fa7936d4000-7fa7936d6000 rwxp 00000000 00:00 0
7fa7936ff000-7fa793700000 r-xp 00027000 08:22 25694742                   /lib/x86_64-linux-gnu/ld-2.27.so
7fa793700000-7fa793701000 rwxp 00028000 08:22 25694742                   /lib/x86_64-linux-gnu/ld-2.27.so
7fa793701000-7fa793702000 rwxp 00000000 00:00 0
7ffe6fb25000-7ffe6fb46000 rwxp 00000000 00:00 0                          [stack]
7ffe6fb8d000-7ffe6fb90000 r--p 00000000 00:00 0                          [vvar]
7ffe6fb90000-7ffe6fb92000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

Podemos verificar que en el segundo caso tenemos muchas más zonas de memoria con permisos de ejecución (--x-) que en la primera, incluyendo heap y stack. 

 

Modificado por última vez enLunes, 01 Junio 2020 10:17
(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.