Imagen: Imágenes de libros de Internet Archive. Modificado por Opensource.com. CC BY-SA 4.0
Después de compilar el mismo código fuente, podemos terminar con diferentes binarios. Depende de qué banderas pasamos a manos del compilador. Algunas de estas banderas le permiten habilitar o deshabilitar una serie de propiedades del binario relacionadas con la seguridad.
Algunos de ellos están habilitados o deshabilitados por el compilador de forma predeterminada. Así es como pueden surgir vulnerabilidades en archivos binarios que no conocemos.
Checksec es una sencilla utilidad para determinar qué propiedades se incluyeron en el momento de la compilación. En este artículo te diré:
- cómo utilizar la utilidad checksec para encontrar vulnerabilidades;
- cómo utilizar el compilador gcc para corregir las vulnerabilidades encontradas.
Instalación de checksec
Para el sistema operativo Fedora y otros sistemas basados en RPM:
$ sudo dnf install checksec
Para sistemas basados en Debian, use apt.
Inicio rápido con checksec
La utilidad checksec consta de un único archivo de secuencia de comandos, que, sin embargo, es bastante grande. Gracias a esta transparencia, puede averiguar qué comandos del sistema para buscar vulnerabilidades en binarios se ejecutan bajo el capó:
$ file /usr/bin/checksec /usr/bin/checksec: Bourne-Again shell script, ASCII text executable, with very long lines $ wc -l /usr/bin/checksec 2111 /usr/bin/checksec
Ejecutemos checksec en la utilidad de exploración de directorios (ls):
$ checksec --file=/usr/bin/ls <strong>RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE</strong> Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH No Symbols Yes 5 17 /usr/bin/ls
Al ejecutar el comando en la terminal, recibirá un informe sobre qué propiedades útiles tiene este binario y cuáles no.
La primera línea es el encabezado de la tabla, que enumera las distintas propiedades de seguridad: RELRO, STACK CANARY, NX, etc. La segunda línea muestra los valores de estas propiedades para el binario de utilidad ls.
¡Hola binario!
Compilaré un binario del código C más simple:
#include <stdio.h>
int main()
{
printf(«Hello World\n»);
return 0;
}
Tenga en cuenta que hasta ahora no he pasado una sola bandera al compilador, con la excepción de -o (no viene al caso, pero simplemente dice dónde mostrar el resultado de la compilación):
$ gcc hello.c -o hello
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, for GNU/Linux 3.2.0, not stripped
$ ./hello
Hello World
Ahora ejecutaré la utilidad checksec para mi binario. Algunas propiedades son diferentes de las propiedades.
ls ( ): $ checksec --file=./hello <strong>RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE</strong> Partial RELRO No canary found NX enabled No PIE No RPATH No RUNPATH 85) Symbols No 0 0./hello
Checksec le permite utilizar una variedad de formatos de salida, que puede especificar con la opción --output. Elegiré el formato JSON y haré que la salida sea más descriptiva con la utilidad jq :
$ checksec --file=./hello --output=json | jq { «./hello»: { «relro»: «partial», «canary»: «no», «nx»: «yes», «pie»: «no», «rpath»: «no», «runpath»: «no», «symbols»: «yes», «fortify_source»: «no», «fortified»: «0», «fortify-able»: «0» } }
Análisis (checksec) y eliminación (gcc) de vulnerabilidades
El archivo binario creado anteriormente tiene varias propiedades que determinan, digamos, el grado de vulnerabilidad. Compararé las propiedades de este archivo con las propiedades del binario ls (también enumerado arriba) y explicaré cómo hacer esto usando la utilidad checksec.
Para cada elemento, además le mostraré cómo eliminar las vulnerabilidades encontradas.
1. Símbolos de depuración
Empezaré simple. Ciertos símbolos se incluyen en el binario en tiempo de compilación. Estos símbolos se utilizan en el desarrollo de software: son necesarios para depurar y corregir errores.
Los símbolos de depuración generalmente se eliminan de la versión del binario que los desarrolladores lanzan para uso general. Esto no afecta el funcionamiento del programa de ninguna manera. Esta limpieza (indicada por la tira de palabras ) se realiza a menudo para ahorrar espacio, ya que el archivo se vuelve más claro después de eliminar los caracteres. Y en el software propietario, estos caracteres a menudo se eliminan también porque los atacantes tienen la capacidad de leerlos en formato binario y utilizarlos para sus propios fines.
Checksec muestra que los símbolos de depuración están presentes en mi binario, pero no en el archivo ls.
$ checksec --file=/bin/ls --output=json | jq | grep symbols «symbols»: «no», $ checksec --file=./hello --output=json | jq | grep symbols «symbols»: «yes»,
La ejecución del comando de archivo puede mostrar lo mismo. Los personajes no se eliminan.
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, for GNU/Linux 3.2.0, <strong>not stripped</strong>
Cómo funciona checksec
Ejecutemos este comando con la opción --debug:
$ checksec --debug --file=./hello
Dado que la utilidad checksec es una secuencia de comandos larga, puede utilizar las funciones de Bash para examinarla. Vamos a mostrar los comandos que ejecuta el script para mi archivo de saludo:
$ bash -x /usr/bin/checksec --file=./hello
Preste especial atención a echo_message, la salida de un mensaje sobre si el binario contiene símbolos de depuración:
+ readelf -W --symbols ./hello
+ grep -q '\.symtab'
+ echo_message '\033[31m96) Symbols\t\033[m ' Symbols, ' symbols=«yes»' '«symbols»:«yes»,'
La utilidad checksec usa el comando readelf con la bandera especial --symbols para leer un archivo binario. Imprime todos los símbolos de depuración en el binario.
$ readelf -W --symbols ./hello
En el contenido de la sección .symtab, puede averiguar la cantidad de símbolos encontrados:
$ readelf -W --symbols ./hello | grep -i symtab
Cómo eliminar los símbolos de depuración después de la compilación
La utilidad de la tira nos ayudará con esto.
$ gcc hello.c -o hello
$
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=322037496cf6a2029dcdcf68649a4ebc63780138, for GNU/Linux 3.2.0, <strong>not stripped</strong>
$
$ strip hello
$
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=322037496cf6a2029dcdcf68649a4ebc63780138, for GNU/Linux 3.2.0, <strong>stripped</strong>
Cómo eliminar símbolos de depuración en tiempo de compilación
Al compilar, use la bandera -s:
$ gcc -s hello.c -o hello
$
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=247de82a8ad84e7d8f20751ce79ea9e0cf4bd263, for GNU/Linux 3.2.0, <strong>stripped</strong>
También puede verificar que los símbolos se hayan eliminado utilizando la utilidad checksec:
$ checksec --file=./hello --output=json | jq | grep symbols «symbols»: «no»,
2. Canarias
Canary (informantes) son valores "secretos" que se almacenan en la pila entre el búfer y los datos de control. Se utilizan para proteger contra ataques de desbordamiento del búfer: si se cambian estos valores, vale la pena hacer sonar la alarma. Cuando se inicia una aplicación, se crea su propia pila para ella. En este caso, es solo una estructura de datos con operaciones push y pop. Un atacante podría preparar datos maliciosos y escribirlos en la pila. En este caso, el búfer puede desbordarse y la pila puede dañarse. En el futuro, esto provocará un bloqueo del programa. El análisis de los valores canarios le permite comprender rápidamente que se ha producido un hackeo y tomar medidas.
$ checksec --file=/bin/ls --output=json | jq | grep canary
«canary»: «yes»,
$
$ checksec --file=./hello --output=json | jq | grep canary
«canary»: «no»,
$
, canary, checksec :
$ readelf -W -s ./hello | grep -E '__stack_chk_fail|__intel_security_cookie'
Enciende canary
Para hacer esto, al compilar, usamos el indicador -stack-protector-all:
$ gcc -fstack-protector-all hello.c -o hello $ checksec --file=./hello --output=json | jq | grep canary «canary»: «yes»,
Ahora checksec puede decirnos con la conciencia tranquila que el mecanismo canario está encendido:
$ readelf -W -s ./hello | grep -E '__stack_chk_fail|__intel_security_cookie'
2: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail@GLIBC_2.4 (3)
83: 0000000000000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail@@GLIBC_2.4
$
3. PIE
La propiedad PIE habilitada permite que el código ejecutable se coloque arbitrariamente en la memoria independientemente de su dirección absoluta:
PIE (Ejecutable independiente de posición): código ejecutable independiente de la posición. La capacidad de predecir dónde y qué áreas de la memoria se encuentran en el espacio de direcciones de un proceso favorece a los atacantes. Los programas de usuario se cargan y ejecutan desde una dirección de memoria virtual de proceso predefinida a menos que se compilen con la opción PIE. El uso de PIE permite que el sistema operativo cargue secciones de código ejecutable en fragmentos arbitrarios de memoria, lo que hace que sea mucho más difícil de descifrar.
$ checksec --file=/bin/ls --output=json | jq | grep pie «pie»: «yes», $ checksec --file=./hello --output=json | jq | grep pie «pie»: «no»,
A menudo, la propiedad PIE solo se incluye al compilar bibliotecas. En el resultado a continuación, hello está marcado como ejecutable LSB y el archivo libc (.so) de la biblioteca estándar está marcado como objeto compartido LSB:
$ file hello
hello: ELF 64-bit <strong>LSB executable</strong>, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, for GNU/Linux 3.2.0, not stripped
$ file /lib64/libc-2.32.so
/lib64/libc-2.32.so: ELF 64-bit <strong>LSB shared object</strong>, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4a7fb374097fb927fb93d35ef98ba89262d0c4a4, for GNU/Linux 3.2.0, not stripped
Checksec obtiene esta información así:
$ readelf -W -h ./hello | grep EXEC Type: EXEC (Executable file)
Si ejecuta el mismo comando para la biblioteca, verá DYN en lugar de EXEC:
$ readelf -W -h /lib64/libc-2.32.so | grep DYN Type: DYN (Shared object file)
Enciende PIE
Al compilar el programa, debe especificar los siguientes indicadores:
$ gcc -pie -fpie hello.c -o hello
Para asegurarse de que la propiedad PIE esté habilitada, ejecute el siguiente comando:
$ checksec --file=./hello --output=json | jq | grep pie «pie»: «yes», $
Ahora nuestro archivo binario (hola) cambiará su tipo de EXEC a DYN:
$ file hello
hello: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=bb039adf2530d97e02f534a94f0f668cd540f940, for GNU/Linux 3.2.0, not stripped
$ readelf -W -h ./hello | grep DYN
Type: DYN (Shared object file)
4. NX
Las herramientas del sistema operativo y del procesador le permiten configurar de manera flexible los derechos de acceso a las páginas de memoria virtual. Al habilitar la propiedad NX (No Execute), podemos evitar que los datos se interpreten como instrucciones del procesador. A menudo, en los ataques de desbordamiento de búfer, los atacantes insertan código en la pila y luego intentan ejecutarlo. Sin embargo, al evitar que el código se ejecute en estos segmentos de memoria, estos ataques pueden evitarse. En la compilación normal con gcc, esta propiedad está habilitada de forma predeterminada:
$ checksec --file=/bin/ls --output=json | jq | grep nx «nx»: «yes», $ checksec --file=./hello --output=json | jq | grep nx «nx»: «yes»,
Checksec vuelve a utilizar el comando readelf para obtener información sobre la propiedad NX. En este caso, RW significa que la pila es de lectura / escritura. Pero dado que esta combinación no contiene el carácter E, existe una prohibición de ejecutar código desde esta pila:
$ readelf -W -l ./hello | grep GNU_STACK GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
Desactivar NX
No se recomienda deshabilitar la propiedad NX, pero puede hacerlo así:
$ gcc -z execstack hello.c -o hello $ checksec --file=./hello --output=json | jq | grep nx «nx»: «no»,
Después de la compilación, veremos que los permisos de la pila han cambiado a RWE:
$ readelf -W -l ./hello | grep GNU_STACK GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10
5. RELRO
En los binarios vinculados dinámicamente, se utiliza una GOT (Tabla de compensación global) especial para llamar a funciones desde bibliotecas. Esta tabla está referenciada por los binarios ELF (Executable Linkable Format). Cuando la protección RELRO (solo lectura de reubicación) está habilitada, el GOT pasa a ser de solo lectura. Esto le permite protegerse contra algunos tipos de ataques que modifican los registros de la tabla:
$ checksec --file=/bin/ls --output=json | jq | grep relro «relro»: «full», $ checksec --file=./hello --output=json | jq | grep relro «relro»: «partial»,
En este caso, sólo una de las propiedades RELRO está habilitada, por lo que checksec genera el valor "parcial". Checksec usa el comando readelf para mostrar la configuración.
$ readelf -W -l ./hello | grep GNU_RELRO GNU_RELRO 0x002e10 0x0000000000403e10 0x0000000000403e10 0x0001f0 0x0001f0 R 0x1 $ readelf -W -d ./hello | grep BIND_NOW
Encienda la protección completa (FULL RELRO)
Para hacer esto, al compilar, necesita usar las banderas apropiadas:
$ gcc -Wl,-z,relro,-z,now hello.c -o hello $ checksec --file=./hello --output=json | jq | grep relro «relro»: «full»,
Eso es todo, ahora nuestro binario ha recibido el título honorífico de FULL RELRO:
$ readelf -W -l ./hello | grep GNU_RELRO GNU_RELRO 0x002dd0 0x0000000000403dd0 0x0000000000403dd0 0x000230 0x000230 R 0x1 $ readelf -W -d ./hello | grep BIND_NOW 0x0000000000000018 (BIND_NOW)
Otras características de checksec
El tema de la seguridad se puede estudiar sin cesar. Incluso hablando de la sencilla utilidad checksec en este artículo, no puedo cubrir todo. Sin embargo, mencionaré algunas posibilidades más interesantes.
Comprobando varios archivos
No es necesario ejecutar un comando por separado para cada archivo. Puede ejecutar un comando para varios binarios a la vez:
$ checksec --dir=/usr/bin
Comprobación de procesos
La utilidad checksec también le permite analizar la seguridad de los procesos. El siguiente comando muestra las propiedades de todos los programas en ejecución en su sistema (necesita usar la opción --proc-all para hacer esto):
$ checksec --proc-all
También puede seleccionar un proceso para verificar especificando su nombre:
$ checksec --proc=bash
Verificación de kernel
Del mismo modo, puede analizar las vulnerabilidades en el kernel de su sistema.
$ checksec --kernel
Prevenido vale por dos
Estudie las propiedades de seguridad en detalle e intente comprender exactamente a qué afecta cada una de ellas y qué tipos de ataques puede prevenir. ¡Checksec para ayudarte!
Los servidores en la nube de Macleod son rápidos y seguros.
Regístrese usando el enlace de arriba o haciendo clic en el banner y obtenga un 10% de descuento durante el primer mes de alquiler de un servidor de cualquier configuración.