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

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

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

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

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
أدوات/GitHubGitHub/aramosf/cve-2026-64468
تصعيد الامتيازاتأطر الاستغلالتحليل الثغرات الأمنيةالاستغلالالتعلم والتعليماستغلال الملفات الثنائية
GitHubaramosf/cve-2026-64468

CVE-2026-64468

إثبات مفهوم غير مميز ورفع صلاحيات محلي لمعمارية x86_64 لثغرة استخدام بعد التحرير (use-after-free) في نواة لينكس Binder (CVE-2026-64468)، مع مختبر KASAN وفرق تفاضلي بين النسخة الضعيفة والمصححة.

عرض المستودع
1منذ 15 أياملم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

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

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

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

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

CVE-2026-64468 — استخدام بعد التحرير في دورة حياة العملية في binder_free_transaction() بنواة لينكس Binder

تشغيل مباشر على QEMU/KVM: النواة غير المُصححة المعرّضة للثغرة تُبلغ عن استخدام بعد التحرير، بينما النواة المُصححة لا تفعل

يحتوي هذا المستودع، لثغرة استخدام بعد التحرير في Binder بنواة لينكس التي تم إصلاحها بواسطة الالتزام العلوي f223d27a546c1e1f48d38fd67760e78f068fe8c4:

  • binder_chain_64468.c — إثبات مفهوم غير مميَّز يصل إلى الثغرة ويجعل النواة تُثبتها، مع مختبر KASAN و مقارنة تفاضلية بين النواة المعرّضة والمُصححة (lab/, run.sh, verify.sh).
  • exploit.c — تصعيد صلاحيات محلي مستقل لمعمارية x86_64. يُترجم باستخدام gcc -O2 -pthread -o exploit exploit.c، ويُشغَّل كمستخدم عادي، وينتهي بصدفة صلاحيات جذر.
  • demo/ — مختبر يُقلع ببيئة مستخدم Debian 13 حقيقية على نواة غير مُصححة، بحيث يمكن ترجمة الاستغلال بواسطة gcc الخاص بالهدف نفسه و تشغيله على الجهاز الذي يستولي عليه بعد ذلك.

يعمل كل شيء كمستخدم عادي (uid/gid 1000، بدون صلاحيات، بدون مساحات أسماء) ضد نواة علوية أصلية بدون أي تصحيحات من أي نوع.

تحذير

تتعمد هذه الشيفرة التنافس على أعمار كائنات النواة ثم تختطف تدفق التحكم في النواة. فقدان السباق يُفسد حالة كومة النواة ويمكن أن يُسبب تعليقًا أو انهيارًا للجهاز. شغّلها فقط في جهاز افتراضي معزول وقابل للتخلص تملكه. لا تشغّلها على مضيف.

الحالة والنطاق

الثغرة

يقرأ binder_free_transaction() العملية الهدف من المعاملة تحت t->lock، ثم يحرر هذا القفل، وبعد ذلك يكتسب القفل الداخلي للهدف:```c spin_lock(&t->lock); target_proc = t->to_proc; spin_unlock(&t->lock);

root@kitploit:~
if (target_proc) {
	binder_inner_proc_lock(target_proc);   /* use after free */
root@kitploit:~
لا شيء يُبقي `target_proc` حيّاً عبر تلك الفجوة. العملية التي يتم تفكيكها
بالتوازي يمكن أن تصل إلى `binder_proc_dec_tmpref() -> kfree()` في ما بين ذلك، لذا
يتم قفل الذاكرة المحرَّرة. الإصلاح في المنبع يثبّت `t->to_thread` بينما
`t->lock` ما زال محتفظاً به، مما يُبقي العملية المالكة حيّة حتى يتم استخدام
القفل الداخلي وتحريره.

تم الإبلاغ عن الثغرة بواسطة **Alice Ryhl** وتم إصلاحها بواسطة **Carlos
Llamas**، وكلاهما من Google. يحمل
[التقرير الأصلي](https://lore.kernel.org/all/[email protected]/)
أثر KASAN المرجعي.

### الوصول إلى المسار القابل للاستغلال

المستدعي الوحيد الذي يمكنه الوصول إلى `to_proc` *الأجنبي* هو
`binder_send_failed_reply()`، وهو لا ينتقل إلى `t->from_parent` إلا عندما يكون
`t->from` هو `NULL`:```c
	target_thread = binder_get_txn_from_and_acq_inner(t);
	if (target_thread) { ...; binder_free_transaction(t); return; }
	next = t->from_parent;
	binder_free_transaction(t);
	t = next;

from_parent يُعيَّن في مكان واحد بالضبط، ويكون هذا التعيين محميًا قبل بضعة أسطر بواسطة فحص مكدس المعاملات السيئ الخاص بـ binder: لا يُسمح للخيط بإرسال معاملة متزامنة إلا إذا كان أعلى مكدسه معاملةً يكون يستقبلها. لذلك، بالنسبة لكل حلقة في تلك السلسلة، يكون مُرسِل الطفل ومُستقبِل الوالد نفس الخيط.

يترتب على ذلك نتيجة حادة. binder_thread_release() يجتاز مكدس الخيط المُحتضَر مع الاحتفاظ بـ proc->inner_lock، ويكتب كلاً من``` iteration j : [holds child->lock ] child->from = NULL ; unlock child->lock spin_lock(parent->lock) iteration j+1 : [holds parent->lock] parent->to_proc = NULL

root@kitploit:~
في **التكرارات المتجاورة لمسار واحد**، كل منها تحت
`t->lock` الخاص بها. لا يكتشف المُتجوّل أن `child->from == NULL` إلا بعد أن يحرّر المسار
قفل `child->lock`، ويحتاج إلى `parent->lock` للقطة الخاصة به. لذا فإن الفرصة
الكاملة هي الفجوة بين `spin_unlock(&child->lock)` لذلك المسار و
`spin_lock(&parent->lock)` الخاص به — بضع تعليمات. المسار المُحرِّر يحمل قفل دوران
ولا يمكن استباقه هناك؛ فقط المقاطعة يمكنها تأخيره.

لهذا السبب تكون السباق ضيقة ولماذا يكون كل من إثبات المفهوم
والاستغلال احتماليين.

### ما يبنيه إثبات المفهوم

`binder_chain_64468.c` يبني أقصر سلسلة تصل إلى
الوصول الضعيف، بحيث يكون عمل المُتجوّل داخل تلك الفجوة صغيرًا قدر ما يسمح به
binder — عمليتا اكتساب لـ `t->lock` و `kfree()` واحدة، دون أي
`inner_proc_lock` خارجي، ولا `wake_up` ولا تسليم رد:```
  B thread i   --e2 (sync, code 0x4442414b)-->  P thread Y_i
  P thread Y_i --e1 (sync, code 0x54414c4c)-->  B thread i   (nested target)

  B thread i   stack: [ e2 outgoing , e1 incoming (top) ]
  P thread Y_i stack: [ e2 incoming , e1 outgoing (top) ]

P ثم يُسقط واصف binder الخاص به، لذا يقوم binder_deferred_release() بتحرير Y_1..Y_K وأخيرًا يحرر binder_proc، بينما كل خيط B يصدر بشكل متزامن BINDER_THREAD_EXIT:``` binder_thread_release(B_i) nulls e1->to_proc (own proc) and e2->from binder_send_failed_reply(e1) e1->from == NULL once Y_i was released -> binder_free_transaction(e1) target_proc already NULL, kfree(e1) -> binder_free_transaction(e2) target_proc == P <-- vulnerable access

root@kitploit:~
العملية الثالثة هي مدير سياق binder، وتُستخدم فقط لتوزيع
المقابض التي تحتاجها العمليتان الأخريان. `exploit.c` يعيد استخدام هذا البناء بالضبط.

### شرطان مستقلان

تقرير KASAN يتطلب كليهما:

* **الوصول الضعيف** — يجب أن يأخذ المتجول `parent->lock` داخل
  النافذة الضيقة أعلاه، بحيث يلتقط لقطة لـ `to_proc` الذي لا يزال حيًا؛ و
* **هبوط التحرير داخل النافذة** — يجب أن يفقد المتجول بعد ذلك وحدة المعالجة المركزية
  بين إسقاط `t->lock` وأخذ القفل الداخلي للضحية، ويبقى بعيدًا عنها
  حتى يكتمل التحرير المؤجل ويُحرَّر `binder_proc`.

الشرط الأول له مؤشر خاص به لا يحتاج إلى KASAN: binder يطبع```
binder: binder_free_proc: Unexpected outstanding_txns -1

عندما يحدث ذلك، لأن المتجول وbinder_thread_release() كلاهما يخفضان نفس العداد لمعاملة واحدة. لاحظ أن هذا ليس فرقًا قابلًا للاستغلال/الإصلاح بحد ذاته — الإصلاح يوقف التحرير، وليس الخفض الثاني — لذلك يظهر على كلا النواتين. يُستخدم هنا فقط لإظهار مسار الكود القابل للاستغلال وهو قيد التنفيذ.

من الاستخدام بعد التحرير إلى الجذر

يتوقف إثبات المفهوم عند خفض 4 بايت من الذاكرة المحررة. تحويل ذلك إلى uid 0 يتطلب أربعة أشياء، ولا يأتي أي منها من الثغرة نفسها: الثغرة لا تسرّب شيئًا.

1. ذاكرة تخزين مؤقت مشتركة

struct binder_proc يبلغ 648 بايت ويُخصص باستخدام GFP_KERNEL kzalloc عادي — وليس __GFP_ACCOUNT. لذلك يقع في kmalloc-1k، جنبًا إلى جنب مع كل تخصيص غير محسوب آخر بنفس الحجم، ولا يكون معزولًا خلف kmalloc-cg-*. هذه الحقيقة الواحدة هي ما يجعل الكائن قابلًا للاسترداد أصلًا.

2. مضخة استرداد تواكب السرعة

لحظة kfree() غير قابلة للملاحظة من مساحة المستخدم، وكذلك لحظة لمس المتجول للكائن مرة أخرى، لذلك لا يوجد شيء للتوقيت بناءً عليه. لذلك يعمل الرش كمضخة: يخصص ويحرر كائنات kmalloc-1k بشكل مستمر، من وحدة المعالجة المركزية التي نفّذت التحرير المؤجل، طالما أن المتجولين يعملون.

تُستخدم رسائل System V لهذا الغرض. alloc_msg() هو kmalloc غير محسوب عادي لرأس 48 بايت بالإضافة إلى الحمولة، لذا فإن رسالة بحجم 976 بايت هي تخصيص بحجم 1024 بايت؛ الحصة لكل قائمة انتظار وليست لكل uid؛ وmsgrcv() يحرر بشكل متزامن. الإنتاجية المُقاسة: ~198,000 تخصيصًا في الثانية، صفر فشل.

جرّب add_key/user_key_payload أولًا وهو فخ. حمولته تُحصى ضد حصة بايت لكل uid (kernel.keys.maxbytes، 20000 افتراضيًا) التي لا تُحرر إلا عندما يدمر جامع القمامة للمفاتيح المفتاح، لذا حلقة تخصيص/تحرير ضيقة تستنزفها في ميلي ثانية: مُقاس 27,151 تخصيصًا ناجحًا مقابل 2,121,009 فشلًا — 98.7% من الرش لا يفعل شيئًا بصمت، وهو ما يبدو تمامًا كرش لا يفوز بالفتحة أبدًا. جعل KEYCTL_INVALIDATE الأمر أسوأ (4,775 نجاحًا)، لأنه يضع عمل جامع القمامة في قائمة الانتظار.

3. ما يجب أن يحتويه الكائن المسترد

يلامس المتجول أربعة حقول من binder_proc المحرر (الإزاحات مُقاسة باستخدام pahole على البناء المستهدف):

مع outstanding_txns == 1 وis_frozen == 1، يصل الخفض إلى الصفر و يستدعي المتجول wake_up_interruptible_all(&proc->freeze_wait). ثم يحسب __wake_up_common curr = head.next - 24 ويستدعي *(head.next - 8): مؤشر دالة يُقرأ من أينما يشير head.next، وهي قيمة يقدمها الكائن المسترد.

4. عنوانان، من قناة جانبية

يجب أن يصل ذلك المؤشر إلى ذاكرة يتحكم فيها المهاجم، بعنوان نواة، والثغرة لا تسرّب شيئًا. يأتي كلا العنوانين من توقيت الجلب المسبق بدلًا من ذلك — نفس القناة مثل KASLD (Brendan Coles، MIT)، الذي يُشتق تنفيذه من هذا:

  • نص النواة. الجلب المسبق لعنوان نواة مُخطط له يُحل في تجول جدول الصفحات وينتهي أسرع بشكل قابل للقياس من عنوان غير مُخطط له، حتى لو لم يصبح الوصول مرئيًا معماريًا. مسح فتحات 2 MiB لنطاق النص يُظهر الصورة كسلسلة من الفتحات السريعة؛ أول فتحة لها هي _text.

  • الخريطة المباشرة. مع CONFIG_RANDOMIZE_MEMORY تكون الخريطة المباشرة عشوائية بوحدات 1 GiB، لذا يجب تحديد موقعها أيضًا. على عكس النص، تغطي كل ذاكرة الوصول العشوائي، لذا فهي أطول سلسلة متصلة من الفتحات المُخططة. كانت هناك حاجة إلى تحسينين لجعلها قابلة للاستخدام:

    • الجلب المسبق الفردي يفصل المُخطط عن غير المُخطط بـ ~4 دورات فقط تحت KVM، وهو ما لا ينجو من الضوضاء، لذا كل عينة توقت دفعة من 400 جلب مسبق (مُقاس 311 مقابل 523 دورة — قابل للفصل)؛
    • المسح الفردي غير موثوق على مضيف مزدحم — 10/10 صحيح مع ضيف واحد في كل مرة، 3/5 مع خمسة ضيوف في وقت واحد — لذا يُشغَّل المسح خمس مرات والأغلبية مطلوبة. بدون إجماع، يبلغ الاستغلال عن الفشل ويتوقف بدلًا من إطلاق النار على عنوان غير مُتحقق منه.

    ما يبدأ عنده التشغيل بشكل موثوق ليس page_offset_base نفسه بل أول فتحة يمكن للنواة أن تخطط بها بصفحة 1 GiB — تلك التي تغطي 4 GiB الفعلية. أسفل ذلك، يفرض ثقب PCI واحتياطات البرامج الثابتة صفحات 2 MiB التي لا يمكن فصل تجولها الأطول عن غير المُخطط هنا. تلك الفتحة هي بالضبط ما تحتاجه المضخة، لذا فهي ما يُستخدم.

ثم ~60% من الذاكرة الفعلية تُملأ بنسخ من صفحة واحدة مصنوعة بحجم 4 KiB، لذا إزاحة ثابتة من ذلك المرساة مدعومة بالصفحة المصنوعة مهما كان التخطيط النهائي.

السلسلة```

freeze_wait.head.next -> entry1 (in the sprayed page)

entry1.func = mov 0x28(%rdi),%rdi ; mov 0x18(%rdi),%rax ; jmp *0x58(%rax) RDI <- entry1+0x28 = &cred RAX <- cred+0x18 jmp *(RAX+0x58) = commit_creds -> commit_creds(cred)

entry2.func = mov $-1,%rax ; ret __wake_up_common breaks out on ret < 0

root@kitploit:~
لا يوجد تبديل مكدس ولا `iretq`: الخيط المختطف يعود من `ioctl()` الخاص به بشكل طبيعي ويعود ببساطة إلى مساحة المستخدم، بصلاحيات الجذر (root).

### بيانات الاعتماد المزورة، والخطأ الذي كان سيضيّعها

تأخذ أداة الإرسال (dispatcher gadget) قيمة `RAX` الخاصة بها من `cred+0x18`، و`cred+0x18` هو
`euid`/`egid`، لذا فورًا بعد السلسلة يكون `euid` هو النصف المنخفض من مؤشر
نواة. هذا أمر تجميلي. ما ليس تجميليًا هو أن `prepare_creds()` —
التي **يستدعيها كل `fork()` و`execve()` لاحقًا** — تقوم بإلغاء مرجعية ثلاثة حقول
بدون أي فحص لـ NULL:```c
	get_group_info(new->group_info);        /* refcount_inc(&gi->usage) */
	get_uid(new->user);                     /* refcount_inc(&u->__count) */
	new->ucounts = get_ucounts(new->ucounts);

بيانات اعتماد مزورة تتركها بقيمة NULL تمنح uid 0 ثم تُحدث انهياراً في الجهاز عند أول execve — أي أن الاستغلال سيُبلغ عن نجاح ثم يدمر الجهاز فوراً. لذلك تشير السلسلة إلى user وucounts وgroup_info نحو المتغيرات العامة الحقيقية للنواة root_user وinit_ucounts وinit_groups، التي تأتي عناوينها من نفس الأساس _text. مع ضبط هذه القيم، يحمل الخيط المُخترَق صلاحية CAP_SETUID فوق init_user_ns، لذا يستدعي setresuid(0,0,0) وتقوم النواة بتثبيت بيانات اعتماد جذر نظيفة مخصصة من النواة فوق البيانات المزورة. فقط بعد ذلك يتم تنفيذ أي شيء آخر.

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

الأنظمة المتأثرة

uname -r ليس كافياً. تقوم نواة الموزّعين عادةً بنقل إصلاحات binder إلى الخلف دون تغيير إلى الإصدار الرئيسي المقابل؛ افحص المصدر أو سجل تغييرات الحزمة.

الإصلاح موسوم بـ Cc: stable، لذا تتلقى فروع stable وفروع الموزّعين نسخاً خلفية منه.

أين الخلل، وأين يمكن الوصول إليه

هذان سؤالان مختلفان، والثاني هو الذي يحدد الأثر.

يُجمَّع الكود الثغري أينما كان CONFIG_ANDROID_BINDER_IPC مضبوطاً. يشمل ذلك توزيعات الأغراض العامة — لكن في جميعها التي تم مسحها يكون السائق وحدة لا تُحمَّل افتراضياً، وحتى عند تحميله، يسجّل init_binder_device() جهاز misc دون ضبط miscdev.mode، لذا ينشئ devtmpfs ملف /dev/binder بصلاحيات 0600 root:root. على Android، يفتحه ueventd بصلاحيات 0666، وهذا بالضبط سبب أهمية الخلل هناك وعدم أهميته غالباً هنا.

إعدادات مقروءة من حزم النواة التي يشحنها الموزّعون أنفسهم:

لذا فإن التعرض العملي على توزيعة سطح مكتب هو غير مباشر: أي شيء يحمّل binder ويفتحه — Waydroid، Anbox، محاكي Android أو بيئة تشغيل حاويات — يعيد إدخال قابلية الوصول الخاصة بـ Android تماماً على جهاز لا تزال نواته تحمل الخلل.

تكلفة كل خيار تحصين على الاستغلال

هذه لا تؤثر على الثغرة؛ بل تؤثر على هذا الاستغلال.

الأهداف المُتحقق منها

كلا النواتين شجرتان رئيسيتان قياسيتان من المنبع. لا شيء مُرقَّع.

CONFIG_KASAN_GENERIC في المختبر الأول هو كاشف، وليس مُفعِّلاً: السباق متطابق بدونه. CONFIG_PREEMPT شرط مسبق حقيقي، وهو ما يشحنه Android.

البنية ليست عاملاً بالنسبة للـ خلل — فهو خطأ في عمر الكائن داخل كود C مستقل عن البنية. وهي عامل كبير جداً بالنسبة للـ استغلال: القطع (gadgets)، وقناة الـ prefetch، وتخطيط الخريطة المباشرة كلها خاصة بـ x86_64.

البناء والتشغيل

المتطلبات: clang، lld، make، cpio، gzip، qemu-system-x86_64، docker (فقط لتجميع rootfs الخاص بـ Debian)، ونسخة محلية من شجرة git الخاصة بـ Linux، وgcc.

الاستغلال، على نظام ثغري بالفعل```sh

gcc -O2 -pthread -o exploit exploit.c ./exploit

root@kitploit:~
بالنسبة لنواة غير تلك الموجودة في `demo/`، استخرج إزاحاتها أولاً:```sh
./mkoffsets.sh /path/to/vmlinux > offsets.h
gcc -O2 -pthread -DEXPLOIT_OFFSETS='"offsets.h"' -o exploit exploit.c

المتغيرات القابلة للضبط، جميعها اختيارية، وجميعها تُقرأ من البيئة: CVE64468_SECONDS, CVE64468_THREADS, CVE64468_SPRAY_PERCENT, CVE64468_CALL_OFFSET_MB, CVE64468_KASLR_ATTEMPTS, CVE64468_DELAY_MAX_US, CVE64468_DELAY_STEP_US, CVE64468_STAGGER_US, CVE64468_VERBOSE, CVE64468_SHELL.

مختبر سلامة الذاكرة```sh

LINUX_GIT=/path/to/linux ./lab/build.sh # x86_64 LINUX_GIT=/path/to/linux TARGET_ARCH=arm64 ./lab/build.sh # arm64 ./run.sh vulnerable ./verify.sh 600 16

root@kitploit:~
النص البرمجي ينشئ شجرتي عمل منفصلتين (worktrees) عند الالتزامات المذكورة أعلاه، ويرفض التشغيل
إذا كانت أي من شجرتي العمل متسخة، ويتحقق من أن الشجرة المعرضة للثغرة تفتقر إلى الإصلاح وأن
الشجرة المُصلحة تحتوي عليه، ويبني كلا النواتين، ويحزم إثبات المفهوم
في initramfs.

### مختبر الاستغلال```sh
./demo/build-kernel.sh     # vulnerable kernel, KASAN off, hardening on
./demo/build-rootfs.sh     # Debian 13 userland + gcc, as an initramfs
./demo/run-demo.sh         # boot it; this is what the recording shows
./demo/verify.sh logs/     # unattended reliability run, one guest per trial

demo/run-demo.sh يقوم بتشغيل الضيف ويسلّم وحدة التحكم إلى المستخدم ذي المعرّف 1000، والذي يقوم بتجميع exploit.c باستخدام gcc الخاص بالضيف نفسه ثم تشغيله.

صور النواة، وأشجار العمل، وأشجار نظام الجذر، والـ initramfs هي نتائج مخبرية ولا يتم تضمينها في نظام التحكم بالنسخ.

عمليات التشغيل الحقيقية

سلامة الذاكرة: النسخة القابلة للاستغلال مقابل النسخة المُصلَحة

docs/example-output.txt هو سجلّ حقيقي للنواة القابلة للاستغلال، بما في ذلك تقرير KASAN؛ وdocs/patched-negative-output.txt هو عنصر التحكم للنواة المُصلَحة؛ وdocs/e2e-results.json هو النتيجة القابلة للقراءة آليًا.

سلسلة الاستدعاءات المُبلَّغ عنها تطابق التقرير الرسمي (upstream) تمامًا:``` BUG: KASAN: slab-use-after-free in queued_spin_lock_slowpath+0x62/0x6a0 Read of size 4 at addr ffff888100282270 by task cve64468-B/91 CPU: 6 UID: 1000 PID: 91 Comm: cve64468-B Not tainted 7.2.0-rc1+ #2 PREEMPT _raw_spin_lock+0x55/0x60 binder_free_transaction+0x80/0x1f0 binder_send_failed_reply+0x98/0x340 binder_thread_release+0x528/0x5e0 binder_ioctl+0x1ca/0xeb0 __x64_sys_ioctl+0x8c9/0xcf0

Allocated by task 93: binder_open+0xb5/0x7b0

Freed by task 9: kfree+0x113/0x310 binder_deferred_func+0x104b/0x1180 process_scheduled_works+0x69f/0xbb0

root@kitploit:~
`UID: 1000` هو الهوية غير المميزة الخاصة بإثبات المفهوم: الضحية
يتم تخصيص `binder_proc` بواسطة `binder_open()`، ويتم تحريره بواسطة قائمة انتظار العمل المؤجلة الخاصة بـ binder، ويتم قراءته بواسطة أداة المسح بعد التحرير.

تشغيل مُسجَّل، 2026-08-16، `./verify.sh 1200 16 3` — ثلاثة ضيوف QEMU/KVM متزامنين لكل متغير، 10 وحدات معالجة مركزية افتراضية لكل منهم، 16 خيطًا، 1200 ثانية لكل متغير، **بدون تصحيحات نواة على أي من الجانبين**:

| | الضعيفة `114a116aaa5f` | المُصلَّحة `f223d27a546c` |
| --- | --- | --- |
| المحاولات | 158,384 | 158,471 |
| عمليات المسح | 2,534,144 | 2,535,536 |
| إخفاقات الإعداد | 0 | 0 |
| الوصول الضعيف (`Unexpected outstanding_txns -1`) | 1,127 | 934 |
| **KASAN `slab-use-after-free`** | **8** | **0** |

هذا هو الفارق: نفس عبء العمل، نفس عدد المحاولات ضمن 0.06%، والاستخدام بعد التحرير يحدث فقط على النواة الضعيفة غير المُصلَّحة.

يظهر الوصول الضعيف على *كلتا* النواتين، وهذا متوقع — الإصلاح يمنع العملية من التحرير داخل النافذة، وليس الإنقاص الثاني لـ `outstanding_txns`. لهذا السبب يُستخدم هذا السطر فقط كأداة تنبؤ رخيصة وليس أبدًا كأساس للفارق.

### تصعيد الامتيازات

![تشغيل QEMU/KVM مباشر: ضيف Debian 13، مستخدم غير مميز يترجم exploit.c باستخدام gcc الخاص بالضيف وينتهي عند موجه root](https://assets.kitploit.com/production/public/readmes/54229/da1e333d7516671ffab64e10d89604ee2a8043de1c48306abc03aaeee88d0d62/205a1253ab216ae4b62a65f761fffb9f0329bcbf9e4feb38563647f68ad2c5e0-display-v1.webp)

`docs/lpe-output.txt` هو سجل حقيقي من ضيف تجريبي، و`docs/lpe-demo.cast` هو التسجيل الكامل غير المُعدَّل بتقنية Asciinema الذي صيغ منه الرسم المتحرك أعلاه (`asciinema play docs/lpe-demo.cast` يعيد تشغيله بالكامل). يحذف الرسم المتحرك الجزء الأوسط الطويل من ذلك التسجيل الذي يتضمن السباق — حيث تبث وحدة التحكم التسلسلية للضيف سجلات تصحيح binder طوال السباق الذي يستغرق ~24 دقيقة تقريبًا، وهو ما قد يُنتج عشرات الميغابايتات من سجل التمرير — مع الإبقاء على مقدمة Debian وموجه root؛ سطر `hit after 26661 attempts ... in 1437s` الخاص بالاستغلال يوضح بالضبط ما تم حذفه. كلاهما تشغيلات مفردة: ضيف لا يفوز ضمن ميزانيته يُطفأ، ويُعاد التسجيل ببساطة بدلاً من تحريره لصنع فوز.

انظر *الموثوقية* أدناه لمعدل النجاح المُقاس.

## الموثوقية

السباق احتمالي على كلا الجانبين، لذا فإن التشغيل الفاشل هو السلوك المتوقع في بعض الأحيان، وليس استغلالًا معطوبًا.

### سلامة الذاكرة

المعدلات المشتقة على النواة الضعيفة، من التشغيل أعلاه:

| الكمية | القيمة |
| --- | --- |
| معدل المحاولات | ~44 محاولة/ثانية لكل ضيف، ~132/ثانية عبر ثلاثة |
| الوصول الضعيف | 7.1e-3 لكل محاولة |
| هبوط التحرير داخل النافذة، بشرط الوصول | 7.1e-3 |
| تقرير KASAN | ~1 لكل 20,000 محاولة، أي تقريبًا واحد كل 2.5 دقيقة بهذا المعدل |

### تصعيد الامتيازات

كل ضيف هو تجربة مستقلة: له KASLR الخاص به، وعشوائية الخريطة المباشرة الخاصة به، والضيف الذي يفوز يتوقف عن السباق. الرقم إذن هو **معدل نجاح لكل إقلاع**، وليس لكل محاولة.

حملة مُسجَّلة، 2026-08-16 23:32 UTC، `GUESTS=6 CPUS=8 MEMORY_MB=9216 ./demo/verify.sh`
— ستة ضيوف QEMU/KVM متزامنين، نواة غير مُصلَّحة `114a116aaa5f`، بيئة مستخدم Debian 13، ميزانية 60 دقيقة لكل منهم، استغلال مُترجم داخل الضيف، بدأ بهوية uid 1000:

| | |
| --- | --- |
| الضيوف الذين وصلوا إلى uid 0 | **2 من 6** |
| الوقت حتى root | 623 ثانية و1,344 ثانية |
| المحاولات في السباق الفائز | 13,839 و31,125 |
| المحاولات لكل ضيف لم يفز | ~95,000 على مدى 3,600 ثانية كاملة |
| إجمالي المحاولات عبر الحملة | 427,676 (6.8 مليون عملية مسح للمكدس) |
| الوصولات الضعيفة الملاحظة (`Unexpected outstanding_txns -1`) | 256 |
| **أعطال النواة أو oopses أو panics** | **0**، في 9 ساعات ضيف |

هناك أمران يستحقان القراءة من هذا الجدول.

**كل سباق مُربَح تقريبًا أصبح root.** مختبر KASAN يقيس احتمال هبوط التحرير داخل النافذة، بشرط الوصول الضعيف، عند 7.1e-3. بتطبيق ذلك على الـ 256 وصولًا الملاحظة هنا، يُتوقع ~1.8 استخدام بعد التحرير عبر الحملة — وتم الحصول على جذرين. الاسترداد واكتشاف العنوان والسلسلة ليست هي عنق الزجاجة؛ السباق هو.

**لا شيء تعطل.** لم يتعرض أي ضيف لـ oops في تسع ساعات ضيف، بما في ذلك الأربعة الذين لم يفزوا أبدًا. إما أن السلسلة تعمل ضد صفحة مُنتثرة ومحددة الموقع بشكل صحيح، أو لا تعمل أبدًا على الإطلاق — وهذا هو الغرض من رفض الأغلبية في المرحلة 2.

هذا الرفض يحدث فعليًا في الممارسة. إقلاع ستة ضيوف في وقت واحد على مضيف محمَّل بالفعل أنتج ضيفًا واحدًا استسلم في المرحلة 2 مع```
[*] direct map not found; refusing to fire at an unverified address

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

يحمل docs/lpe-results.json الشكل القابل للقراءة آليًا، بما في ذلك SHA-256 للنواة الدقيقة وinitramfs المستخدمة.

لماذا عدة ضيوف، ولماذا مضيف مفرط الاشتراك

تشغيل الضيوف بشكل متزامن ليس فقط من أجل التوازي. على مضيف مزدحم، يقوم KVM بإلغاء جدولة vCPUs الخاصة بالضيوف، وهذا هو بالضبط التأخير الذي تحتاجه الحالة الثانية: يجب أن يفقد المُنقّب وحدة المعالجة المركزية بين إسقاط t->lock وأخذ القفل الداخلي للضحية. تم قياس ضيف واحد على مضيف خامل بحوالي واحد على عشرين من معدل الوصول الضعيف مقارنة بثلاثة ضيوف متزامنين.

لكن هناك حدًا أعلى. عند ثمانية ضيوف بواقع 8 vCPUs لكل منهم على مضيف بثلاثين خيطًا، مع حوالي 5 جيجابايت من رش الخريطة المباشرة لكل ضيف، دخل المضيف في وضع التبديل ولم يُحرز ثلاثة من الضيوف الثمانية أي تقدم على الإطلاق. ستة هو الإعداد الافتراضي المُسلم به.

تم قياس خيوط المساعدة الاختيارية للاستباق داخل الضيف بتكلفة حوالي ثلاثة أضعاف معدل المحاولة دون تحسين معدل الإصابة، ولا يتم استخدامها.

اتضح أن أحد العوامل البيئية أكثر أهمية مما كان متوقعًا: مخرجات التصحيح الخاصة بـ binder. مع ضبط binder.debug_mask على قيمته الافتراضية، يُصدر برنامج التشغيل كمية كبيرة من حركة pr_info المحدودة المعدل أثناء السباق، والضغط الناتج عن printk وقفل وحدة التحكم يُطيل نافذة الاستباق الدقيقة التي تحتاجها الحالة الثانية. إسكاته باستخدام binder.debug_mask=0 للحصول على وحدة تحكم أنظف — وهو الأمر الواضح للتسجيل — خفض معدل الإصابة بشكل ملحوظ في الاختبار: عمل الضيوف بعدًا كبيرًا بعد عدد محاولات الفائزين دون إصابة. لذلك يُبقي demo/run-demo.sh تصحيح binder على الإعداد الافتراضي، وتعد وحدة التحكم الهادئة خيارًا صريحًا. هذه خاصية للمختبر، وليست للاستغلال — لكنها توضيح جيد لمدى اعتماد هذا السباق على التذبذب الزمني على مستوى النظام بدلاً من أي شيء يتحكم فيه الاستغلال نفسه.

السلامة

  • كلا الضيفين عبارة عن صور initramfs قابلة للاستهلاك. لا يُكتب أي شيء على قرص الضيف أو المضيف.
  • إثبات المفهوم يفتح فقط /dev/binder، ويرسل معاملات binder ويُنهي خيوط binder. لا يُثبّت شيئًا ولا يترك شيئًا خلفه.
  • يكتب الاستغلال ملفًا واحدًا: نسخة من نفسه بصلاحيات setuid-root، تُستخدم لنقل الامتياز من الخيط الفائز إلى العملية الأم. يقوم بإلغاء ربط تلك النسخة قبل تنفيذ الصدفة، لذا لا يبقى أي شيء setuid بعد التشغيل.
  • السباق الخاسر يمكن أن يُسبب ذعرًا أو تعليقًا للضيف؛ يُقلع مختبر سلامة الذاكرة مع panic=1 oops=panic بحيث ينتهي التشغيل بدلاً من الاستمرار في حالة تالفة. أعد التشغيل دائمًا من إقلاع نظيف.

الاعتمادات

  • مؤلف الاستغلال: أ. راموس <[email protected]> (تويتر: @aramosf)
  • اكتشاف الثغرة والإبلاغ عنها: أليس رايل، Google
  • الإصلاح المنبع: كارلوس لاماس، Google
  • القناة الجانبية لـ KASLR بالجلب المسبق: مشتقة من KASLD، حقوق الطبع والنشر (c) 2019 بريندان كولز، مرخصة بموجب MIT. المتغير ذو الخريطة المباشرة هو عمل جديد هنا.

البحث عن الاستغلال العام

تم التحقق في 2026-08-16 باستخدام SearchSploit (نسخة محلية من Exploit-DB) والبحث على الويب عن CVE-2026-64468 وbinder_free_transaction وf223d27a546c. لم يتم العثور على استغلال عام أو إثبات مفهوم لهذا CVE؛ يُرجع SearchSploit فقط إدخالات binder القديمة غير ذات الصلة لنظام Android. هذا فحص في نقطة زمنية محددة، وليس ضمانًا دائمًا.

تنزيل الأداة
الحالةالادعاء
مؤكَّدالثغرة حقيقية، ويمكن الوصول إليها من عملية غير مميَّزة، والإصلاح العلوي يزيلها.
مُثبَتإلغاء الإشارة المعرّض للثغرة إلى binder_proc المحتضر يُتحقق منه طبيعيًا وبشكل متكرر على النواة غير المُصححة، ولا يحدث أبدًا على النواة المُصححة.
مُثبَتاستخدام التحرير الكامل، كما أبلغ عنه KASAN، على النواة غير المُصححة.
مُثبَتاستعادة binder_proc المُحرَّر ببايتات يتحكم بها المهاجم، واختطاف تدفق التحكم في النواة، وتصعيد الصلاحيات إلى uid 0 من مستخدم غير مميَّز، على معمارية x86_64.
غير مُدَّعىأي نتيجة على جهاز بائع أو جهاز Android محدد. فقط النواة العلوية المذكورة أدناه تم اختبارها، على معمارية x86_64.
غير مُدَّعىأن الاستغلال المُرفق يعمل دون تعديل ضد نواة توزيعة. انظر أي الأنظمة المتأثرة: يتطلب إزاحات خاصة بكل نواة، وعلى كل توزيعة عامة تم مسحها، لا يمكن لمستخدم غير مميَّز الوصول إلى جهاز binder من الأساس.
الإزاحةالحقلما يفعله المتجول
108int outstanding_txnsيخفضه
113bool is_frozenيقرأه
120wait_queue_head_t freeze_waitيتجول فيه إذا كان outstanding_txns == 0 && is_frozen
624spinlock_t inner_lockيلتقطه ويحرره
الحالةالالتزامملاحظات
السلالة المُدخَلةa370003cc301مُسمّاة بوسم Fixes: من المنبع
تم التحقق من ثغريته114a116aaa5fالأب المباشر للإصلاح؛ يحمل إصلاح الثغرة المجاورة CVE-2026-64469، لذا يعزل الزوجان CVE-2026-64468 وحدهما
التصحيح الرئيسيf223d27a546cالإصلاح قيد الاختبار
التوزيعةالنواةANDROID_BINDER_IPCالجهازSLAB_BUCKETSRANDOM_KMALLOC_CACHESقابل للوصول بدون صلاحيات؟
Debian 13 (trixie)6.12.101mANDROID_BINDER_DEVICES="binder"، BINDERFS معطّلyمعطّللا — الوحدة غير محمّلة؛ /dev/binder بصلاحيات 0600
Debian 12 (bookworm)6.1.0mANDROID_BINDER_DEVICES="binder"، BINDERFS معطّلغير متاح (قبل 6.11)غير متاح (قبل 6.6)لا — نفسه
Ubuntu 24.04 LTS6.8.0mBINDERFS=m، ANDROID_BINDER_DEVICES=""غير متاح (قبل 6.11)yلا — يتطلب صلاحيات root لتنفيذ mount -t binder
Ubuntu 22.04 LTS5.15.0mBINDERFS=m، ANDROID_BINDER_DEVICES=""غير متاحغير متاحلا — نفسه
Android (AOSP / الموزّع)6.1، 6.6، 6.12 GKIy/dev/binder، /dev/hwbinder، /dev/vndbinder——نعم — السائق هو IPC المنصة وهو متاح للجميع
الخيارالأثر هنا
CONFIG_SLAB_BUCKETS (6.11+)قاتل لهذا الاسترداد. يعزل msg_msg في مجموعات kmalloc الخاصة به، لذا لا يمكن للمضخة أبداً أن تستقر في فتحة binder_proc. Debian 13 يضبطه. يجب إيجاد تخصيص آخر غير محسوب بحجم 1 KiB. النواة 6.6، وهي ما تعمل عليه أجهزة Android المعنية، تسبقه بالكامل.
CONFIG_RANDOM_KMALLOC_CACHES (6.6+)يقسم kmalloc-1k إلى عدة مخابئ حسب موقع الاستدعاء، لذا يجب أن تصيب المضخة نفس المخبأ؛ ضريبة 1 من 16 على الاسترداد، وليست جداراً. Ubuntu يضبطه، Debian لا يضبطه.
عزل جداول الصفحات (nopti غير مستخدم)قاتل لاكتشاف العناوين. لا يمكن للـ prefetch رؤية نص النواة مع تفعيل PTI، ويكتشف الاستغلال ذلك ويتوقف. PTI مُجمَّع في كل توزيعة أعلاه، لكن المعالج يقرر ما إذا كان نشطاً: فهو معطّل على العتاد غير المتأثر بـ Meltdown، وهو المكان الذي قيست فيه هذه النتائج.
CONFIG_SLAB_FREELIST_RANDOM، ..._HARDENEDمفعّلان في المختبر، كما يشحنها الموزّعون. لا أثر قابل للقياس: المضخة لا تتنبأ بترتيب القائمة الحرة، بل تخصص ببساطة عدداً كبيراً من الكائنات.
KASLR (RANDOMIZE_BASE، RANDOMIZE_MEMORY)مفعّل. يُهزم بمراحل الـ prefetch؛ دون nokaslr.
إزاحات خاصة بكل نواةتحتاج السلسلة إلى commit_creds، وثلاثة متغيرات عامة مرتبطة بالاعتمادات، وقطعتين (gadgets)، كإزاحات من _text. يستخرجها mkoffsets.sh من vmlinux مستهدف؛ دونها يطلق الاستغلال النار على عناوين خاطئة. هذه خاصية خاصة بكل بناء لأي استغلال نواة، وليست دفاعاً.
مختبر سلامة الذاكرة (lab/)مختبر الاستغلال (demo/)
إصدار النواة7.2.0-rc1+7.2.0-rc1+
الالتزام الثغري114a116aaa5f0295376cdf12da743c5bce3b20ceنفسه
الالتزام المُصحَّحf223d27a546c1e1f48d38fd67760e78f068fe8c4— (يُقاس الاستغلال على النواة الثغرية فقط)
البنيةx86_64 (KVM) وarm64 (TCG)x86_64 (KVM)
المترجمUbuntu clang 21.1.8 / LLD 21.1.8نفسه
KASANمفعّل — هو الكاشفمعطّل — يغيّر تخطيط الـ slab ويجعل أي استرداد غير تمثيلي
تحصين الـ slab—SLAB_FREELIST_RANDOM، SLAB_FREELIST_HARDENED مفعّلان؛ SLAB_BUCKETS، RANDOM_KMALLOC_CACHES معطّلان
KASLR—RANDOMIZE_BASE، RANDOMIZE_MEMORY مفعّلان
بيئة المستخدمinitramfs أدنىDebian GNU/Linux 13 (trixie)، مع gcc الخاص بالتوزيعة
الهوية الابتدائيةuid 1000، gid 1000، دون صلاحيات، دون مساحات أسماءنفسه
سطر أوامر الإقلاعconsole=ttyS0 rdinit=/init panic=1 oops=panic kasan_multi_shot=1console=ttyS0 loglevel=4 rdinit=/init — دون nopti، دون nokaslr، دون mitigations=off