ld -z c贸digo separado


Este art铆culo se centrar谩 en una peque帽a caracter铆stica de seguridad agregada en GNU ld a la versi贸n 2.30 en diciembre de 2018. En ruso, esta mejora se mencion贸 en opennet con la siguiente anotaci贸n:


Modo "-z c贸digo separado", que aumenta la seguridad de los archivos ejecutables a costa de un peque帽o aumento en el tama帽o y el consumo de memoria

Vamos a resolverlo. Para explicar de qu茅 tipo de problema de seguridad estamos hablando y cu谩l es la soluci贸n, comencemos con las caracter铆sticas generales de las vulnerabilidades de vulnerabilidad binaria.


Explotar problemas de control de flujo


Un atacante puede transferir datos al programa y manipularlos de esta manera con la ayuda de varias vulnerabilidades: escribir por 铆ndice m谩s all谩 del l铆mite de la matriz, copiar de forma insegura las cadenas, usar objetos despu茅s del lanzamiento. Tales errores son t铆picos para el c贸digo del programa C y C ++ y pueden provocar da帽os en la memoria con ciertos datos de entrada para el programa.


Vulnerabilidades de corrupci贸n de memoria

CWE-20: Validaci贸n de entrada incorrecta
CWE-118: Acceso incorrecto de recurso indexable ('Error de rango')
CWE-119: Restricci贸n incorrecta de operaciones dentro de los l铆mites de un b煤fer de memoria
CWE-120: Copia de b煤fer sin verificar el tama帽o de la entrada ('Desbordamiento de b煤fer cl谩sico')
CWE-121: desbordamiento de b煤fer basado en pila
CWE-122: desbordamiento de b煤fer basado en el mont贸n
CWE-123: Condici贸n de escribir qu茅-d贸nde
CWE-124: Buffer Underwrite ('Buffer Underflow')
CWE-125: Lectura fuera de l铆mites
CWE-126: Lectura excesiva del b煤fer
CWE-127: Buffer Under-read
CWE-128: error de ajuste
CWE-129: Validaci贸n incorrecta del 铆ndice de matriz
CWE-130: Manejo incorrecto de la inconsistencia del par谩metro de longitud
CWE-131: C谩lculo incorrecto del tama帽o del b煤fer
CWE-134: Uso de cadenas de formato controladas externamente
CWE-135: C谩lculo incorrecto de la longitud de cadena de varios bytes
CWE-170: Terminaci贸n nula incorrecta
CWE-190: Desbordamiento de enteros o envolvente
CWE-415: Doble gratis
CWE-416: Uso despu茅s de gratis
CWE-476: Desreferencia de puntero nulo
CWE-787: Escritura fuera de l铆mites
CWE-824: Acceso del puntero no inicializado
...


El elemento de explotaci贸n cl谩sico de las vulnerabilidades similares a la corrupci贸n de la memoria es sobrescribir un puntero en la memoria. Luego, el programa utilizar谩 el puntero para transferir el control a otro c贸digo: para llamar a un m茅todo o funci贸n de clase desde otro m贸dulo, para regresar desde una funci贸n. Y como el puntero se sobrescribi贸, el atacante interceptar谩 el control, es decir, se ejecutar谩 el c贸digo preparado por 茅l. Si est谩 interesado en variaciones y detalles de estas t茅cnicas, le recomendamos leer el documento .


Este momento com煤n de la operaci贸n de tales exploits es conocido, y aqu铆 para el atacante las barreras se han colocado durante mucho tiempo:


  1. Comprobaci贸n de la integridad de los punteros antes de pasar el control: cookies de pila, protecci贸n de flujo de control, autenticaci贸n de puntero
  2. Aleatorizaci贸n de direcciones de segmento con c贸digo y datos: aleatorizaci贸n del dise帽o del espacio de direcciones
  3. Evitar que el c贸digo ejecute segmentos de c贸digo externos: protecci贸n de espacio ejecutable

A continuaci贸n, nos centraremos en proteger este 煤ltimo tipo.


protecci贸n de espacio ejecutable


La memoria del programa es heterog茅nea y est谩 dividida en segmentos con diferentes derechos: leer, escribir y ejecutar. Esto est谩 garantizado por la capacidad del procesador para marcar p谩ginas de memoria con marcas de acceso en las tablas de p谩ginas. La idea de protecci贸n se basa en una estricta separaci贸n de c贸digo y datos: los datos recibidos del atacante en el proceso de procesamiento deben colocarse en segmentos no ejecutables (pila, mont贸n) y el c贸digo del programa en s铆, en segmentos separados inmutables . Por lo tanto, esto deber铆a privar al atacante de la capacidad de colocar y ejecutar c贸digo extra帽o en la memoria.


Para evitar la prohibici贸n de la ejecuci贸n de c贸digo en segmentos de datos, se utilizan t茅cnicas de reutilizaci贸n de c贸digo. Es decir, el atacante transfiere el control a los fragmentos de c贸digo (en lo sucesivo denominados gadgets) ubicados en las p谩ginas ejecutables. Las t茅cnicas de este tipo son de diversa dificultad, en orden creciente:


  • pasar el control a una funci贸n que hace lo suficiente para el atacante: a la funci贸n system () con un argumento controlado para ejecutar comandos de shell arbitrarios (ret2libc)
  • transferir el control a una funci贸n o cadena de dispositivos que deshabilitar谩n la protecci贸n o har谩n que parte de la memoria sea ejecutable (por ejemplo, llamando a mprotect() ), seguido de la ejecuci贸n de c贸digo arbitrario
  • ejecuci贸n de todas las acciones deseadas utilizando una larga cadena de gadgets

Por lo tanto, el atacante se enfrenta a la tarea de reutilizar el c贸digo existente en un volumen u otro. Si esto es algo m谩s complicado que volver a una sola funci贸n, se requerir谩 una cadena de gadgets . Para buscar gadgets por segmentos ejecutables, hay herramientas: ropper , ropgadget .


Hoyo READ_IMPLIES_EXEC


Sin embargo, a veces las 谩reas de memoria con datos pueden ser ejecutables, y los principios de separaci贸n de c贸digo y datos descritos anteriormente se violan claramente. En tales casos, el atacante se ahorra la molestia de encontrar dispositivos o funciones para reutilizar el c贸digo. Un hallazgo interesante de este tipo fue la pila ejecutable y todos los segmentos de datos en el mismo "firewall industrial".


Listado /proc/$pid/maps :


 00008000-00009000 r-xp 00000000 08:01 10 /var/flash/dmt/nx_test/a.out 00010000-00011000 rwxp 00000000 08:01 10 /var/flash/dmt/nx_test/a.out 00011000-00032000 rwxp 00000000 00:00 0 [heap] 40000000-4001f000 r-xp 00000000 1f:02 429 /lib/ld-linux.so.2 4001f000-40022000 rwxp 00000000 00:00 0 40027000-40028000 r-xp 0001f000 1f:02 429 /lib/ld-linux.so.2 40028000-40029000 rwxp 00020000 1f:02 429 /lib/ld-linux.so.2 4002c000-40172000 r-xp 00000000 1f:02 430 /lib/libc.so.6 40172000-40179000 ---p 00146000 1f:02 430 /lib/libc.so.6 40179000-4017b000 r-xp 00145000 1f:02 430 /lib/libc.so.6 4017b000-4017c000 rwxp 00147000 1f:02 430 /lib/libc.so.6 4017c000-40b80000 rwxp 00000000 00:00 0 be8c2000-be8d7000 rwxp 00000000 00:00 0 [stack] 

Aqu铆 puede ver la tarjeta de memoria del proceso de utilidad de prueba. Un mapa consta de 谩reas de memoria: filas de tabla. Primero, preste atenci贸n a la columna derecha: explica el contenido del 谩rea (segmentos de c贸digo, datos de las bibliotecas de funciones o el programa en s铆) o su tipo (mont贸n, pila). A la izquierda, en orden, se encuentra el rango de direcciones que ocupa cada 谩rea de memoria y, adem谩s, las banderas de derechos de acceso: r (lectura), w (escritura), x (ejecuci贸n). Estas banderas determinan el comportamiento del sistema cuando se intenta leer, escribir y ejecutar memoria en estas direcciones. Si se viola el modo de acceso designado, se produce una excepci贸n.


Tenga en cuenta que casi toda la memoria dentro del proceso es ejecutable: la pila, el mont贸n y todos los segmentos de datos. Esto es un problema Obviamente, la presencia de p谩ginas de memoria rwx facilitar谩 la vida de un atacante, ya que podr谩 ejecutar libremente su c贸digo en dicho proceso en cualquier lugar donde su c贸digo llegue al transferir datos (paquetes, archivos) a dicho programa para su procesamiento.


驴Por qu茅 surgi贸 tal situaci贸n en un dispositivo moderno que admite la prohibici贸n de la ejecuci贸n de c贸digo en p谩ginas de datos con hardware, la seguridad de las redes corporativas e industriales depende del dispositivo y el problema sonoro y su soluci贸n se conocen desde hace mucho tiempo?


Esta imagen est谩 determinada por el comportamiento del n煤cleo durante la inicializaci贸n del proceso (asignaci贸n de una pila, mont贸n, carga del ELF principal, etc.) y durante la ejecuci贸n de las llamadas al proceso nuclear. El atributo clave que afecta esto es el indicador de personalidad READ_IMPLIES_EXEC . El efecto de este indicador es que cualquier memoria legible tambi茅n se vuelve ejecutable. Se puede establecer un indicador en su proceso por varias razones:


  1. El indicador de software en el encabezado ELF puede solicitar expl铆citamente Legacy para implementar un mecanismo muy interesante: un trampol铆n en la pila ( 1 , 2 , 3 ).
  2. Puede ser heredado por procesos hijos del padre.
  3. 隆Puede ser instalado por el kernel independientemente para todos los procesos! Primero, si la arquitectura no admite memoria no ejecutable. En segundo lugar, por si acaso, para soportar otras muletas antiguas . Este c贸digo est谩 en el kernel 2.6.32 (ARM), que tuvo una vida 煤til muy larga. Este fue solo nuestro caso.

Espacio para encontrar gadgets en una imagen ELF


Las bibliotecas de funciones y los ejecutables del programa est谩n en formato ELF. El compilador gcc traduce las construcciones de lenguaje en c贸digo m谩quina y lo coloca en una secci贸n, y los datos que este c贸digo opera en otras secciones. Hay muchas secciones y el enlazador ld las agrupa en segmentos. Por lo tanto, ELF contiene una imagen de programa que tiene dos representaciones: una tabla de secciones y una tabla de segmentos.


 $ readelf -l /bin/ls Elf file type is EXEC (Executable file) Entry point 0x804bee9 There are 9 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x08048034 0x08048034 0x00120 0x00120 RE 0x4 INTERP 0x000154 0x08048154 0x08048154 0x00013 0x00013 R 0x1 [Requesting program interpreter: /lib/ld-linux.so.2] LOAD 0x000000 0x08048000 0x08048000 0x1e40c 0x1e40c RE 0x1000 LOAD 0x01ef00 0x08067f00 0x08067f00 0x00444 0x01078 RW 0x1000 DYNAMIC 0x01ef0c 0x08067f0c 0x08067f0c 0x000f0 0x000f0 RW 0x4 NOTE 0x000168 0x08048168 0x08048168 0x00044 0x00044 R 0x4 GNU_EH_FRAME 0x018b74 0x08060b74 0x08060b74 0x00814 0x00814 R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10 GNU_RELRO 0x01ef00 0x08067f00 0x08067f00 0x00100 0x00100 R 0x1 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame 03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.ABI-tag .note.gnu.build-id 06 .eh_frame_hdr 07 08 .init_array .fini_array .jcr .dynamic .got 

Aqu铆 puede ver la asignaci贸n de secciones a segmentos en una imagen ELF.


Las utilidades utilizan la tabla de secciones para analizar programas y bibliotecas, pero los cargadores no la utilizan para proyectar archivos ELF en la memoria de proceso. La tabla de secci贸n describe la estructura ELF con m谩s detalle que la tabla de segmentos. Varias secciones pueden estar dentro de un segmento.


Los cargadores ELF crean una imagen ELF en memoria basada en el contenido de la tabla de segmentos . La tabla de particiones ya no se usa para cargar ELF en la memoria.


Pero hay excepciones a esta regla.

Por ejemplo, en la naturaleza, hay un parche de desarrolladores de Debian para el cargador ELF ld.so para la arquitectura ARM, que busca una secci贸n especial ".ARM.attributes" como SHT_ARM_ATTRIBUTES y binarios con una tabla de secci贸n recortada en dicho sistema.


Un segmento ELF tiene banderas que determinan qu茅 permisos tendr谩 el segmento en la memoria. Tradicionalmente, la mayor铆a del software para GNU / Linux se organiz贸 de tal manera que se PT_LOAD dos PT_LOAD (cargables en memoria) en la tabla de segmentos, como en la lista anterior:


  1. Segmento con banderas RE


    1.1. C贸digo ejecutable ELF: secciones .init , .text , .fini


    1.2. Datos inmutables en ELF: .symtab , .rodata


  2. Segmento de banderas RW


    2.1. Datos variables en ELF: secciones .plt , .got , .data , .bss



Si presta atenci贸n a la composici贸n del primer segmento y sus banderas de acceso, queda claro que tal dise帽o expande el espacio para buscar dispositivos para t茅cnicas de reutilizaci贸n de c贸digo. En ELF grandes, como libcrypto, las tablas de servicio y otros datos inmutables pueden ocupar hasta el 40% del segmento ejecutable . La presencia de algo similar a piezas de c贸digo en estos datos se confirma por los intentos de desmontar dichos archivos binarios con una gran cantidad de datos en el segmento ejecutable sin tablas de secci贸n y s铆mbolos. Cada secuencia de bytes en este 煤nico segmento ejecutable puede considerarse 煤til para el fragmento de ataque del c贸digo de m谩quina y el trampol铆n, ya sea esta secuencia de bytes con al menos una parte de la l铆nea del mensaje de depuraci贸n del programa, parte del nombre de la funci贸n en la tabla de s铆mbolos o el n煤mero constante del algoritmo criptogr谩fico ...


Encabezados ejecutables de PE

Los encabezados y tablas ejecutables al comienzo del primer segmento de la imagen ELF se parecen a la situaci贸n de Windows hace aproximadamente 15 a帽os. Hubo una serie de virus que infectaron archivos, escribiendo su c贸digo en su encabezado PE, que tambi茅n era ejecutable all铆. Logr茅 desenterrar tal muestra en el archivo:


Virus.Win32.Haless.1127


Como puede ver, el cuerpo del virus se aprieta justo despu茅s de la tabla de secciones en el 谩rea de los encabezados de PE. En una proyecci贸n de un archivo en la memoria virtual, generalmente hay alrededor de 3 KB de espacio libre aqu铆. Despu茅s del cuerpo del virus hay un espacio vac铆o y luego la primera secci贸n comienza con el c贸digo del programa.


Sin embargo, para Linux hubo trabajos mucho m谩s interesantes de la escena VX: Represalias .


Soluci贸n


  • El problema descrito anteriormente se conoce desde hace mucho tiempo .
  • Corregido el 12 de enero de 2018 : se agrega la clave `ld -z separate-code: encabezado de segmento" Crear c贸digo separado "PT_LOAD" en el objeto. Esto especifica un segmento de memoria que debe contener solo instrucciones y debe estar en p谩ginas totalmente separadas de cualquier otro dato. No cree un segmento de c贸digo separado "PT_LOAD" si se usa el c贸digo noseparate "). La caracter铆stica fue lanzada en la versi贸n 2.30 .
  • Adem谩s, esta caracter铆stica se incluy贸 de manera predeterminada en la pr贸xima versi贸n 2.31 .
  • Presente en paquetes binutils nuevos, por ejemplo, en los repositorios Ubuntu 18.10. Muchos paquetes ya se han ensamblado con esta nueva caracter铆stica, que el investigador de ElfMaster encontr贸 y document贸

Como resultado de los cambios en el algoritmo de dise帽o, se obtiene una nueva imagen ELF:


 $ readelf -l ls Elf file type is DYN (Shared object file) Entry point 0x41aa There are 11 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align PHDR 0x000034 0x00000034 0x00000034 0x00160 0x00160 R 0x4 INTERP 0x000194 0x00000194 0x00000194 0x00013 0x00013 R 0x1 [Requesting program interpreter: /lib/ld-linux.so.2] LOAD 0x000000 0x00000000 0x00000000 0x01e6c 0x01e6c R 0x1000 LOAD 0x002000 0x00002000 0x00002000 0x14bd8 0x14bd8 RE 0x1000 LOAD 0x017000 0x00017000 0x00017000 0x0bf80 0x0bf80 R 0x1000 LOAD 0x0237f8 0x000247f8 0x000247f8 0x0096c 0x01afc RW 0x1000 DYNAMIC 0x023cec 0x00024cec 0x00024cec 0x00100 0x00100 RW 0x4 NOTE 0x0001a8 0x000001a8 0x000001a8 0x00044 0x00044 R 0x4 GNU_EH_FRAME 0x01c3f8 0x0001c3f8 0x0001c3f8 0x0092c 0x0092c R 0x4 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x10 GNU_RELRO 0x0237f8 0x000247f8 0x000247f8 0x00808 0x00808 R 0x1 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt 03 .init .plt .plt.got .text .fini 04 .rodata .eh_frame_hdr .eh_frame 05 .init_array .fini_array .data.rel.ro .dynamic .got .data .bss 06 .dynamic 07 .note.ABI-tag .note.gnu.build-id 08 .eh_frame_hdr 09 10 .init_array .fini_array .data.rel.ro .dynamic .got 

El l铆mite entre el c贸digo y los datos ahora es m谩s preciso. El 煤nico segmento ejecutable realmente contiene solo secciones de c贸digo: .init, .plt, .plt.got, .text, .fini.


驴Qu茅 ha cambiado exactamente dentro de ld?

Como sabe, la secuencia de comandos del vinculador describe la estructura del archivo ELF de salida. Puede ver el script predeterminado como este:


 $ ld --verbose GNU ld (GNU Binutils for Ubuntu) 2.26.1 * * * using internal linker script: ================================================== /* Script for -z combreloc: combine and sort reloc sections */ /* Copyright (C) 2014-2015 Free Software Foundation, Inc. * * * 

Muchos otros scripts para diferentes plataformas y combinaciones de opciones se encuentran en el directorio ldscripts . Se han creado nuevos scripts para la opci贸n de separate-code .


 $ diff elf_x86_64.x elf_x86_64.xe 1c1 < /* Default linker script, for normal executables */ --- > /* Script for -z separate-code: generate normal executables with separate code segment */ 46a47 > . = ALIGN(CONSTANT (MAXPAGESIZE)); 70a72,75 > . = ALIGN(CONSTANT (MAXPAGESIZE)); > /* Adjust the address for the rodata segment. We want to adjust up to > the same address within the page on the next page up. */ > . = SEGMENT_START("rodata-segment", ALIGN(CONSTANT (MAXPAGESIZE)) + (. & (CONSTANT (MAXPAGESIZE) - 1))); 

Aqu铆 puede ver que se ha agregado una directiva para declarar un nuevo segmento con secciones de solo lectura que siguen al segmento de c贸digo.


Sin embargo, adem谩s de los scripts, se realizaron cambios en las fuentes del vinculador. A saber, en la funci贸n _bfd_elf_map_sections_to_segments - vea commit . Ahora, al seleccionar segmentos para secciones, se agregar谩 un nuevo segmento cuando la secci贸n difiera por el indicador SEC_CODE de la secci贸n anterior.


Conclusi贸n


Como antes , recomendamos que los desarrolladores no se olviden de utilizar los indicadores de seguridad integrados en el compilador y el vinculador al desarrollar software. Un cambio tan peque帽o puede complicar en gran medida la vida del atacante y hacer que la suya sea mucho m谩s tranquila.

Source: https://habr.com/ru/post/es433108/


All Articles