Si te gustan las mitigaciones de exploits, es posible que hayas escuchado hablar de un nuevo sistema de llamada llamado mseal que se incorporó en la versión 6.10 del kernel de Linux, proporcionando una protección llamada “sellado de memoria”. Más allá de las notas de los autores, existe muy poca información sobre esta mitigación. En este artículo, explicaremos qué es esta syscall, incluyendo cómo difiere de los esquemas de protección de memoria anteriores y cómo funciona en el kernel para proteger la memoria virtual. También describiremos los escenarios de explotación específicos que mseal ayuda a detener en el espacio de usuario de Linux, como detener la modificación maliciosa de permisos y prevenir ataques de desasignación de memoria.
¿Qué es mseal (y qué no es)?
El sellado de memoria permite a los desarrolladores hacer que las regiones de memoria sean inmutables frente a modificaciones ilícitas durante la ejecución del programa. Cuando se sella un rango de direcciones de memoria virtual (VMA), un atacante con un primitivo de ejecución de código no puede realizar operaciones de memoria virtual posteriores para cambiar los permisos del VMA o modificar cómo se organiza para su beneficio.
Si eres como yo y seguiste el discurso apasionante que rodeó a esta syscall en las listas de correo del kernel, es posible que hayas observado que el equipo de seguridad de Chrome la introdujo para apoyar su estrategia de CFI de V8, inicialmente para ChromeOS basado en Linux. Después de una larga deliberación y varios reescritos, finalmente se incorporó en el kernel, con planes para expandir su caso de uso más allá de los navegadores con su integración en glibc, posiblemente en la versión 2.41.
Las garantías de seguridad de mseal son diferentes a las de Linux’s memfd_create y su variante memfd_secret, que proporcionan sellado de archivos. memfd_create y memfd_secret permiten crear archivos anónimos respaldados por RAM como alternativa a almacenar contenido en tmpfs, con memfd_secret que va un paso más allá al asegurar que la región de memoria sea accesible solo para el proceso que posee el descriptor de archivo. Esto permite a los desarrolladores crear “mapeos de enclave seguro” en el espacio de usuario que pueden proteger datos sensibles en memoria.
mseal se diferencia de los esquemas de protección de memoria anteriores en Linux porque es una syscall diseñada específicamente para mitigar exploits contra atacantes remotos que buscan ejecución de código, en lugar de atacantes locales que buscan exfiltrar secretos sensibles en memoria.
Para entender las mitigaciones de seguridad de mseal, debemos estudiar su implementación para entender cómo opera. Afortunadamente, mseal es simple de entender, así que veamos cómo funciona en el kernel.
Un vistazo bajo la capa
mseal tiene una firma de función simple:
int mseal(unsigned long start, size_t len, unsigned long flags)
start y len representan el rango de inicio y fin de un VMA válido que queremos sellar, y len debe estar alineado con la página.
flags están sin usar en el momento de escribir y deben establecerse en 0.
En el kernel 6.12, su definición de syscall llama a do_mseal:
static int do_mseal(unsigned long start, size_t len_in, unsigned long flags)
{
size_t len;
int ret = 0;
unsigned long end;
struct mm_struct *mm = current->mm; // [1]
// ... Verificar flags == 0, verificar alineación de página y calcular `end`
if (mmap_write_lock_killable(mm)) // [2]
return -EINTR;
/*
* Primera pasada, esto ayuda a evitar
* sellado parcial en caso de error en el rango de direcciones de entrada,
* por ejemplo, error ENOMEM.
*/
ret = check_mm_seal(start, end); // [3]
if (ret)
goto out;
/*
* Segunda pasada, esto debería tener éxito, a menos que haya errores
* desde vma_modify_flags, por ejemplo, error de fusión/división o proceso
* que alcanza el máximo de VMAs admitidos, sin embargo, esos casos deberían ser raros.
*/
ret
~ gcc stack_no_sealing.c -o stack_no_sealing ~ ./stack_no_sealing
Introduzcamos el sellado de memoria en el rango de VMA de la pila que contiene el código shell:
int main(void)
{
/* representa la pila que ahora contiene el código shell /bin/sh que hemos esparcido de alguna manera */
unsigned char exec_shellcode[] =
"\xe1\x45\x8c\xd2\x21\xcd\xad\xf2\xe1\x65\xce\xf2\x01\x0d\xe0\xf2"
"\xe1\x8f\x1f\xf8\xe1\x03\x1f\xaa\xe2\x03\x1f\xaa\xe0\x63\x21\x8b"
"\xa8\x1b\x80\xd2\xe1\x66\x02\xd4";
/* calcula el inicio de la página para el código shell */
void (*exec_ptr)() = (void(*)())&exec_shellcode;
void *exec_offset = (void *)((int64_t) exec_ptr & ~(getpagesize() - 1));
/* sella la página de la pila que contiene el código shell! */
if (mseal(exec_offset, getpagesize()) < 0)
handle_error("mseal");
// vulnerabilidad activada, puntero de instrucción secuestrado
/* ======= lo que nuestra cadena de ROP haría: ======= */
mprotect(exec_offset, getpagesize(), PROT_READ|PROT_WRITE|PROT_EXEC);
/* falla de segmentación ahora, ya que no se produjo ningún cambio de permiso */
exec_ptr();
return 0;
}
La comprobación can_modify_vma mencionada anteriormente se activa cuando se llama a mprotect, lo que evita que se produzca el cambio de permiso y el intento de ejecutar el código shell falla:
~ gcc stack_with_sealing.c -o stack_with_sealing ~ ./stack_with_sealing [1] 48771 falla de segmentación (core dumped) ./stack_with_sealing
Una estrategia simple para adaptarse a software del mundo real podría implicar introducir una versión macro-izada del código de mseal y sellar páginas de manera selectiva en frames de pila donde podrían residir datos no confiables para la explotación:
#define SIMPLE_HARDEN_NX_SINGLE_PAGE(frame)
do {
void *frame_offset = (void *)((int64_t) &frame & ~(getpagesize() - 1));
if (mseal(frame_offset, getpagesize()) == -1) {
handle_error("mseal");
}
} while(0)
int frame_2(void)
{
int frame_start = 0;
unsigned char another_untrusted_buffer[1024] = { 0 };
SIMPLE_HARDEN_NX_SINGLE_PAGE(frame_start);
return 0;
}
int frame_1(void)
{
unsigned char untrusted_buffer[1024] = { 0 };
SIMPLE_HARDEN_NX_SINGLE_PAGE(untrusted_buffer);
return frame_2();
}
Incluso si un VMA sellado se reutiliza como un frame para otra función con logica de sellado, invocar mseal nuevamente se consideraría una operación no válida, por lo que no surgirían errores. Sin embargo, los desarrolladores deben tener en cuenta casos límite como la expansión automática de la pila debido a un uso agresivo o características personalizadas como la división de la pila.
Esperemos que, a medida que se integre mseal en glibc, veamos aparecer ajustes que no requieran el uso manual de la syscall para la pila. Los comentaristas en la lista de correo de LWN anhelan un sellado automático que pueda activarse para aplicaciones más simples.
Y con todo esto dicho, si un atacante no quiere realizar un ROP completo y insisten en revivir la nostalgia del código shell, siempre pueden utilizar su técnica de reutilización de código inicial para mmap una región fresca que sea ejecutable. Sin embargo, esto es bastante laborioso, ya que ahora implica copiar la carga útil de la explotación desde una región legible a este nuevo mapa. Mitigación de la explotación basada en la desasignación de memoria
No permitir mprotect también impide que una región sellada se vuelva legible, lo que es valioso si hay variables de datos que, cuando se modifican, podrían mejorar un primitivo de explotación. Sin embargo, durante la creación de mseal, los mantenedores de Chrome racionalizaron una técnica más fácil y poderosa con el beneficio adicional de evitar CFI (integridad de flujo de control). Determinaron que si un atacante puede pasar un puntero corrupto a las syscalls de desasignación/reasignación, pueden “perforar un agujero” en la memoria que podría ser rellenado con datos controlados por el atacante. Esto no violaría las garantías de CFI, ya que la integridad de flujo de control hacia adelante y hacia atrás solo cubriría las transiciones de flujo de control manipuladas (por ejemplo, direcciones de retorno de la pila y punteros de función).
Esto es increíblemente atractivo para un navegador que implementa un compilador JIT. El Turbofan de V8 puede crear regiones que cambian entre RW y RX, lo que ayuda al proceso de relleno y cambio de permisos. Por lo tanto, un atacante puede aprovechar el proceso de compilación JIT emitiendo código ejecutable desde JavaScript de camino caliente en la región no asignada para sobrescribir datos críticos y luego aprovechar las modificaciones para obtener ejecución de código.
Argumentamos que esta es una técnica de explotación solo de datos, ya que no implica secuestrar directamente el flujo de control o requerir punteros filtrados, sino más bien manipular datos específicos en la memoria que influyen en el flujo de control a gusto del atacante. En una era de mitigaciones como CFI, esta ha surgido como una técnica bastante potente durante la explotación. Por lo tanto, el sellado de memoria puede prevenir estas técnicas solo de datos específicas al no permitir escenarios de perforación de agujeros.
Esta técnica de explotación solo de datos no es solo para navegadores con compiladores JIT. Una técnica similar sería la Casa de Muney para la explotación de la pila de usuarios. Como señala Max Dulin en su publicación, Qualys utilizó esta técnica para realizar una explotación real para un error antiguo en Qmail.
Esta técnica se basa en el hecho de que para bloques de asignación grandes (mayores que el umbral de ajuste M_MAP_THRESHOLD), malloc y free invocarán directamente mmap y munmap, respectivamente, sin listas de asignación intermedias que almacenen bloques liberados (lo que ayuda a simplificar enormemente la explotación). Dado que los metadatos de tamaño existen en la parte superior de los bloques asignados, manipularlos a un tamaño de página diferente y liberarlos provocaría una desasignación en las regiones de memoria adyacentes al bloque. Dulin utilizó la desasignación arbitraria para apuntar a las regiones .gnu.hash y .dynsym y, después de rellenarlas con un bloque de mmap más grande, habilitó la sobrescritura de una sola entrada de PLT sin resolver, reviviendo un ataque de sobrescritura de GOT.
Dulin tiene un PoC muy bien hecho y anotado para este ataque aquí. Aquí está una versión abreviada que va hasta el punto en que se produce la desasignación y el relleno:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <malloc.h>
// Con este tamaño de asignación,
// malloc es equivalente a mmap
// free es equivalente a munmap
#define THRESHOLD_SIZE 0x100000
int main() {
long long *bottom, *top, *refill;
bottom = malloc(THRESHOLD_SIZE);
memset(bottom, 'B', THRESHOLD_SIZE);
// [1] Asignación que escribimos fuera de los límites de un bloque anterior
top = malloc(THRESHOLD_SIZE);
memset(top, 'A', THRESHOLD_SIZE);
// [2] Corrompe el campo de tamaño, asegurando la alineación de página + el bit de mmap
// tamaño para desasignar = top + bottom + tamaño arbitrario grande
int unmap_size = (0xfffffffd & top[-1]) + (0xfffffffd & bottom[-1]) + 0x14000;
top[-1] = (unmap_size | 2);
// Activar munmap con bloque corrupto
free(top);
// [3] Rellenar con un nuevo bloque de mmap más grande
refill = malloc(0x5F0000);
memset(refill, 'X', 0x5F0000);
return 0;
}
Es probable que haya muchos otros casos de uso y escenarios que no cubrimos. Después de todo, mseal es el nuevo integrante en el kernel de Linux. A medida que la integración de glibc se complete y madure, esperamos ver iteraciones mejoradas para la syscall para satisfacer demandas específicas, incluyendo la definición del uso final del parámetro de flags.
Es importante destacar que mseal es una herramienta poderosa para mejorar la seguridad del software, y su uso puede variar dependiendo de las necesidades específicas de cada proyecto. Algunos posibles casos de uso adicionales podrían incluir:
Proteger la memoria de la pila y la pila de llamadas para evitar ataques de desbordamiento de buffer
Proteger la memoria de los datos para evitar ataques de lectura y escritura no autorizadas
Proteger la memoria de la pila de ejecución para evitar ataques de ejecución de código arbitrario
En resumen, mseal es una herramienta valiosa para mejorar la seguridad del software, y su uso puede variar dependiendo de las necesidades específicas de cada proyecto. Es importante investigar y comprender cómo mseal puede ser utilizado para mejorar la seguridad del software y proteger contra ataques maliciosos.
Este es un pequeño edit en progreso. Para ver mis updates más recientes seguime en twitter Luego lo terminaré en el transcurso de la semana.
negrita
Un header
No hay mucho para contar, solo probando algunas cosas de markdown Here’s a code chunk:
#!/bin/bash
echo "algo asi"
#include <stdio.h>
int main () {
printf("o algo asi con syntax highlighting\n");
return 0;
}
Boxes
cajitas
Notification
Note: caja de notificacion.
Warning
Warning: caja de advertencia.
Error
Error: cajita de error
Imagen hosteada localmente
No se rian de mi ayudamemoria