لینک کوتاه مطلب : https://hsgar.com/?p=3943

نکاتی در مورد خرابی حافظه راه دور OpenSSL – Guido Vranken

OpenSSL نسخه 3.0.4 که در 21 ژوئن 2022 منتشر شد، در معرض تخریب حافظه راه دور است که می تواند به طور پیش پاافتاده توسط یک مهاجم فعال شود. BoringSSL، LibreSSL و شاخه OpenSSL 1.1.1 تحت تأثیر قرار نمی گیرند. علاوه بر این، فقط سیستم های x64 با پشتیبانی AVX512 تحت تأثیر قرار می گیرند. اشکال است درست شد در مخزن اما نسخه جدید هنوز در انتظار است.

تا حدودی عجیب و غریب، تقریبا هیچ کس در مورد این صحبت نمی کند. اگر بهره برداری RCE امکان پذیر باشد، در ارزیابی شدت جدا شده، آن را بدتر از Heartbleed می کند، اگرچه شعاع انفجار بالقوه به دلیل این واقعیت محدود است که بسیاری از افراد هنوز از درخت 1.1.1 به جای 3 استفاده می کنند، libssl به LibreSSL و BoringSSL نفوذ کرده است. این آسیب‌پذیری تنها یک هفته است که وجود دارد (HB سال‌هاست وجود داشت) و یک CPU با قابلیت AVX512 مورد نیاز است.

این پست پیشینه ای در مورد چگونگی پیدایش این باگ و نکاتی در مورد قابلیت بهره برداری از آن ارائه می دهد.

در 31 مه 2022، مشکلی را در زمینه قدرت مدولار مونتگومری در زمان ثابت در OpenSSL و BoringSSL (اما نه LibreSSL) پیدا کردم و گزارش کردم. برای برخی از مقادیر B, E, M برای کدام B ^ E % M == 0، برخی از توابع برمی گردند M به جای 0؛ نتیجه به طور کامل کاهش نمی یابد.

مشخص شد که چهار مسیر کد متمایز تحت تأثیر این وجود دارد:

دیوید بنجامین از گوگل موضوع را به طور گسترده تحلیل کرد و دریافتند که این اشکال یک خطر امنیتی ایجاد نمی کند (حداقل نه در داخل، تماس گیرندگان خارجی بسته به آنچه که در تلاش برای محاسبه هستند ممکن است در حالت های نادرست قرار بگیرند). جالب اینجاست که یک ظاهری هم پیدا کرد اشکال در کاغذ توسط Shay Gueron که کد RSAZ بر اساس آن است.

این من را گرفت فوزر زمان زیادی برای پیدا کردن، زیرا شانس پیدا کردن B ^ M % E = 0 برای بزرگ، Nمقادیر بیت از B, E, M در جایی که این مقادیر نیمه تصادفی هستند کوچک هستند، بنابراین من اقدام به اضافه کردن a کردم حل کننده توان مدولار، بنابراین، اکنون می توان باگ را به سرعت پیدا کرد، و امیدواریم که به یافتن باگ های مشابه بیشتر در آینده (در OpenSSL یا جاهای دیگر) کمک کند. (عملکرد Z3 در حل معادلات شامل توان مدولار بسیار ضعیف است، بنابراین مجبور شدم معادلات خودم را حل کنم).

در ثابت که روی کد دوگانه 1024 RSAZ اعمال شده اشتباه است زیرا تابع کاهش نامیده می شود با num اندازه بیت را تنظیم کنید، جایی که باید تعداد آن باشد BN_ULONG عناصر (که همیشه 8 بایت بزرگ هستند، زیرا این اندازه در سیستم های x64 به اندازه طول بدون علامت است، که تنها معماری است که می تواند از AVX512 پشتیبانی کند). بنابراین با 1024 بیت بودن اندازه ورودی، به جای 128، به 8192 بایت (خواندن یا نوشتن به) دسترسی پیدا می کند.

به قسمت داخلی اشکال. 5 آرایه مجزا درگیر هستند. 3 آرایه رونویسی می شوند.

متغیر شرح اندازه اختصاص داده شده بیش از حد خواندن/نوشتن جمع خواندن/نوشتن؟
res1 نتیجه modexp 1 128 896 8192 بخون بعد بنویس
m1 مدول 1 128 896 8192 خواندن
res2 نتیجه modexp 1 128 896 8192 بخون بعد بنویس
m2 مدول 2 128 896 8192 خواندن
storage فضا را خراش دهید 1184 7296 8192 بنویس بعد بخون
  • از 8192 بایت خوانده می شود res1، res2، m1، m2 و storage
  • 8192 بایت نوشته شده است res1، res2 و storage (این جایی است که خرابی حافظه رخ می دهد)
  • اگر در نظر بگیریم res1_bn به یک bignum شامل res1[0..8192] (که در آن آخرین بایت مهم ترین بایت است) و m1_bn بودن m1[0..8192]، سپس اگر res1_bn < m1_bn، محتویات res1[0..8192] پس از بازنویسی بدون تغییر باقی می ماند. همین امر برای res2 و m2.
  • این بدان معناست که اگر بتوانید مهم ترین بیت را تنظیم کنید m1[8191] به 1 و مهم ترین بیت از res1[8191] به 0، سپس res1[0..8192] حالت اولیه خود را حفظ خواهد کرد (و هیچ فساد واقعی رخ نمی دهد). این شرایط ممکن است به طور تصادفی رخ دهد. همین امر برای res2 و m2.
  • برعکس، اگر res1_bn >= m1_bn، سپس پس از نوشتن، res1[N] یکی از خواهد بود {res1[N], res1[N] - m1[N], res1[N] - m1[N] - 1}. همین امر برای res2 و m2.
  • پس از اینکه رونویسی ها اتفاق افتاد، storage[N]خواهد بود ~(m2[N] - res2[N]) یا ~(m2[N] - res2[N])+1.
  • از این نتیجه می شود که اگر کنترل کنید m2[N]و res2[N]، شما بیشتر کنترل می کنید storage[N].
  • محتویات اصلی از storage هرگز خوانده نمی شود، بنابراین به هیچ وجه بر وضعیت پایانی تأثیر نمی گذارد.

خلاصه شده:

متغیر ارزش پس از نوشتن
Res1[N] res1[N] یا res1[N] - m1[n] یا res1[N] - m1[N] - 1
Res2[N] res2[N] یا res2[N] - m2[n] یا res2[N] - m2[N] - 1
ذخیره سازی[N] ~(m2[N] - res2[N]) یا ~(m2[N] - res2[N]) + 1
وضعیت های پس از نوشتن آسیب پذیری OpenSSL

هر کدام از این لم ها مستقل از ورودی های تابع modexp و هر متغیر یا حالت دیگری درست هستند.

اینجا یک فوزر است که این متغیرها را نشان می دهد.

مکانیک تفریق (پیچیدن) در اینجا جالب است زیرا می توانید از آنها برای اعمال نشانگر دلتا به یک نشانگر تابع استفاده کنید تا آن را به یک تابع متفاوت نشان دهید و این می تواند به دور زدن کمک کند. ASLR، زیرا در حالی که آدرس های تابع تصادفی هستند، فاصله آنها برای هر باینری مشخص ثابت است.

به عنوان مثال، اگر:

  • تک، خاص BN_ULONG در res1 آرایه حاوی یک اشاره گر فعال به یک تابع خاص است
  • و شما می توانید کنترل کنید BN_ULONG که در m1 در همان شاخص (به عنوان مثال از طریق اسپری پشته)
  • و بقیه ایالت کاملا ناشناخته و کنترل نشده است

سپس با تنظیم m1[N] به oldfunc - newfunc، نشانگر تابع اصلی دقیقاً خواهد بود newfunc پس از سرریز با احتمال 25 درصد:

void goodfunc(void) { printf("goodn"); }
void badfunc(void) { printf("badn"); }
int main(void)
{
    /* Indices 0..127 are within allocated bounds */
    /* Beyond that is either unallocated or allocated by different parts
     * of the program.
     */
    struct {
        BN_ULONG storage[1024], res1[1024], m1[1024], res2[1024], m2[1024];
    } vars;

    /* Randomize everything; not known to the attacker */
    FILE* fp = fopen("/dev/urandom", "rb");
    assert(fread(&vars, 1, sizeof(vars), fp));
    fclose(fp);

    /* Assume res1[345] contains a function pointer used internally by OpenSSL */
    vars.res1[345] = (BN_ULONG)(&goodfunc);
    /* Assume we control m1[345] */
    vars.m1[345] = (BN_ULONG)(&goodfunc) - (BN_ULONG)(&badfunc);

    bn_reduce_once_in_place(vars.res1, /*carry=*/0, vars.m1, vars.storage, 1024);
    bn_reduce_once_in_place(vars.res2, /*carry=*/0, vars.m2, vars.storage, 1024);

    void (*fnptr)(void) = (void*)vars.res1[345];
    fnptr(); /* good or bad? */
    return 0;
}

OpenSSL به شدت از نشانگرهای تابع استفاده می کند. در حال دویدن find -name '.c' -exec grep 'METH. = {' {} ; | grep -v test از ریشه مخزن بیش از 130 ساختار داده را نشان می دهد که مجموعه ای از نشانگرهای تابع را در خود محصور می کند. تفریق دلتا ممکن است در سوء استفاده از این شرایط برای ایجاد رفتار نادرست OpenSSL با درجات مختلف از شدت مفید باشد.

به غیر از اجرای کد، سناریوهایی نیز وجود دارد که در آن داده های خصوصی به مهاجم نشت می کند.

فرض:

  • R¹ = res[I..J]
  • M¹ = m[I..J]
  • I، K >= 128
  • J، L <= 1023

فرض کنید R¹ یک فضای اختصاص یافته است که مهاجم می تواند بخواند و بنویسد (به عنوان مثال یک حالت TLS داخلی که مقدار آن تا حدی با نحوه دست دادن توسط مهاجم تعیین می شود، و تا حدی می توان آن را با نحوه خواندن آن خواند. کد TLS متعاقباً بر اساس وضعیت خود رفتار می کند).

اجازه دهید M¹ حاوی نوعی اطلاعات سری باشد.

به یاد بیاورید که ترکیبی از res[N] (قبل از بازنویسی) و m[N] به داخل نشت می کند res[N]. نتیجه این است که اگر مهاجم دسترسی خواندن و نوشتن به ، M¹ را می توان به طور جزئی یا کامل استنباط کرد.

این چیزی است که من تا کنون استنباط کرده ام. ممکن است خطاهایی در این پست وجود داشته باشد. لطفا به guido@guidovranken.com ایمیل بزنید و من آنها را تصحیح می کنم و به شما اعتبار می دهم. تو می توانی من را در توییتر دنبال کنید.



لینک منبع

ارسال یک پاسخ

آدرس ایمیل شما منتشر نخواهد شد.