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

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

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

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

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
TLPE — # CVE-2026-49881 ثغرة منطقية في فئة `InCallController` في خدمة الاتصالات (Telecom) بنظام Android 17 تسمح لتطبيق غير مميَّز بالحصول على تنفيذ تعسفي للكود بصلاحيات UID 1000 system_server. | Kitploit
أدوات/GitHubGitHub/supersonic/tlpe
أمان أندرويدتصعيد الامتيازاتآليات الاستمراريةتحليل الثغرات الأمنيةالاستغلالأمن الجوالاستغلال الملفات الثنائية
GitHubsupersonic/tlpe

TLPE

# CVE-2026-49881 ثغرة منطقية في فئة `InCallController` في خدمة الاتصالات (Telecom) بنظام Android 17 تسمح لتطبيق غير مميَّز بالحصول على تنفيذ تعسفي للكود بصلاحيات UID 1000 system_server.

عرض المستودع
111منذ 10س 59دلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

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

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

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

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

هذا هو إثبات مفهوم (PoC) وتقرير فني لثغرة CVE-2026-49881، وهي مشكلة منطقية في فئة InCallController في خدمة الاتصالات (Telecom) بنظام Android 17 تسمح لتطبيق غير مميز بالحصول على تنفيذ تعليمات برمجية عشوائي بمعرف المستخدم UID 1000 الخاص بـ system_server دون أي تفاعل إضافي من المستخدم. نُظهر هنا أيضًا أن تنفيذ التعليمات البرمجية في system_server لا يزال من السهل تكييفه لتحقيق الثبات (Persistence)، حتى في إصدارات Android الحديثة.

أبلغت عن هذه الثغرة إلى فريق أمان Android في 2026-04-10، وتم تأكيدها في 2026-05-06، وتم إصلاحها في نشرة أمان Android لشهر سبتمبر 2026. (انظر هنا للتصحيح)

في وقت الإبلاغ، كانت تؤثر فقط على إصدارات Pixel التي تعمل بنظام Android 16 QPR3 وما بعده (بالإضافة إلى إصدارات 17 Beta) على حد علمي، لكنها انتقلت لاحقًا إلى الإصدار المستقر من AOSP لنظام Android 17.

لحماية نفسك من هذه المشكلة، تأكد من تثبيت تحديث نظام Google Play بالإضافة إلى تصحيح أمان النظام. (Telecom هو مكوّن رئيسي (mainline) منذ Android 17)

TLPE

ملاحظات حول إثبات المفهوم (PoC)

  • يوضح إثبات المفهوم الحصول على تنفيذ تعليمات برمجية في system_server باستخدام الثغرة، ويسجل id وتتبع المكدس (stack trace) إلى logcat، ويعيد تثبيت نفسه كمكوّن من مكونات system_server.
  • بمجرد تثبيت إثبات المفهوم، سيؤدي النقر على زر "بدء الاستغلال" (Start Exploit) أو إجراء مكالمة عبر حزمة الاتصالات (telecom stack) إلى تشغيله.
  • قم بتجميع إثبات المفهوم عن طريق تشغيل ملف build.sh المرفق. بخلاف ذلك، يمكنك تشغيل ./gradlew assembleSystemRelease يدويًا، ثم نقل ملف app-system-release.apk الناتج إلى app/src/poc/assets/system.apk، ثم تشغيل ./gradlew assemblePocRelease.
  • سيقوم إثبات المفهوم بتسجيل شهادته الخاصة كشهادة سلف (ancestor certificate) لمعرف المستخدم UID 1000 بعد الاستغلال الناجح. تستمر هذه الحالة عبر تحديثات OTA بما في ذلك تصحيح الثغرة نفسها. لتنظيف جهازك بعد تشغيل إثبات المفهوم، يجب الضغط على زر "إلغاء التثبيت" (Uninstall) في إثبات المفهوم (الذي ينظف الشهادة المحقونة) - ومع ذلك، أوصي بشدة باستخدام مخزن مفاتيح الإصدار (release keystore) الخاص بك لتوقيع ملف APK المُجمَّع لإثبات المفهوم أثناء الاختبار. (بدلاً من الذي يستخدمه إثبات المفهوم افتراضيًا في TLPE/app/teststore.jks)
  • لاحظ أن إثبات المفهوم بعد الدخول إلى system_server يقوم أيضًا بتعطيل حماية Play Protect عن طريق تعيين package_verifier_user_consent إلى -1 في Settings.Global لأنه قد يعترض أحيانًا معاملة إعادة التثبيت بسبب التوقيعات غير المعروفة. يجب إعادة تمكين هذا في الإعدادات بعد الاختبار.
  • تم التحقق من صحته على Pixel Android و AOSP، ولكن مرحلة تنفيذ التعليمات البرمجية يجب أن تعمل عبر إصدارات Android 17 المخصصة من قبل الشركات المصنعة (OEM). قد تتطلب مرحلة إعادة التثبيت كـ system_server بعض التخصيص حسب الشركة المصنعة لأنها تعتمد على اجتياز بنية PMS باستخدام رموز قد تغيرها الشركات المصنعة أحيانًا.

مخرجات logcat المتوقعة لإثبات المفهوم هي:

root@kitploit:~
04-15 03:02:47.911  1558 12775 E TLPE    : ===================================
04-15 03:02:47.911  1558 12775 E TLPE    : [+] Exploit successful!
04-15 03:02:47.911  1558 12775 E TLPE    : [+] Running as: [uid=1000(system) gid=1000(system) groups=1000(system),1001(radio),1002(bluetooth),1003(graphics),1004(input),1005(audio),1006(camera),1007(log),1008(compass),1009(mount),1010(wifi),1018(usb),1021(gps),1023(media_rw),1024(mtp),1032(package_info),1065(reserved_disk),3001(net_bt_admin),3002(net_bt),3003(inet),3005(net_admin),3006(net_bw_stats),3007(net_bw_acct),3009(readproc),3010(wakelock),3011(uhid),3012(readtracefs) context=u:r:system_server:s0]
04-15 03:02:47.911  1558 12775 E TLPE    : [+] Current stack trace:
04-15 03:02:47.911  1558 12775 E TLPE    : [dalvik.system.VMStack.getThreadStackTrace(Native Method), java.lang.Thread.getStackTrace(Thread.java:2842), poc.sithi.tlpe.EvilFactory.instantiateClassLoader(EvilFactory.kt:31), android.app.LoadedApk.createOrUpdateClassLoaderLocked(LoadedApk.java:1215), android.app.LoadedApk.getClassLoader(LoadedApk.java:1267), android.app.ContextImpl.getClassLoader(ContextImpl.java:542), com.android.server.telecom.InCallController.serviceClassExists(InCallController.java:2561), com.android.server.telecom.InCallController.getInCallServiceComponents(InCallController.java:2606), com.android.server.telecom.InCallController.getInCallServiceComponents(InCallController.java:2515), com.android.server.telecom.InCallController.getInCallServiceComponents(InCallController.java:2499), com.android.server.telecom.InCallController.bindToBTService(InCallController.java:2247), com.android.server.telecom.InCallController.onCallAdded(InCallController.java:1437), com.android.server.telecom.CallsManager.addCall(CallsManager.java:5457), com.android.server.telecom.CallsManager.processIncomingCallIntent(CallsManager.java:1970), com.android.server.telecom.callsequencing.voip.IncomingCallTransaction.processTransaction(IncomingCallTransaction.java:76), com.android.server.telecom.callsequencing.CallTransaction$$ExternalSyntheticLambda3.apply(R8$$SyntheticClass:0), java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1126), java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:458), com.android.server.telecom.LoggedHandlerExecutor$1.loggedRun(LoggedHandlerExecutor.java:41), android.telecom.Logging.Runnable$1.run(Runnable.java:37), android.os.Handler.handleCallback(Handler.java:1095), android.os.Handler.dispatchMessageImpl(Handler.java:135), android.os.Handler.dispatchMessage(Handler.java:125), android.os.Looper.loopOnce(Looper.java:269), android.os.Looper.loop(Looper.java:367), android.os.HandlerThread.run(HandlerThread.java:139)]
04-15 03:02:47.911  1558 12775 E TLPE    : ===================================
04-15 03:02:47.934  1558 12785 E TLPE    : [+] Retrieved system APK, attempting persistence...
04-15 03:02:47.935  1558 12785 E TLPE    : [+] Injection successful, forcing packages.xml flush
04-15 03:02:47.957  1558 12785 E TLPE    : [+] Persistence successful, reinstalling...

التقرير الفني (Writeup)

هذه ثغرة مباشرة بشكل غير معتاد. كلما حدثت بعض الإجراءات المتعلقة بالاتصالات (Telecom)، يحاول InCallController اكتشاف الخدمات المتاحة من خلال getInCallServiceComponents. يحدث هذا بشكل طبيعي عند تسجيل مكالمة مع النظام، ولكن يمكن للتطبيق تشغيله عند الطلب أيضًا بفضل واجهة برمجة تطبيقات المعاملات TelecomManager.addCall. (ملاحظة: هذا ما يستخدمه زر "بدء الاستغلال" في إثبات المفهوم) تتطلب هذه الواجهة إذن MANAGE_OWN_CALLS، لكنه إذن عادي وغير مرئي للمستخدم ويتم منحه تلقائيًا عند التثبيت.

يتم تنفيذ هذا التعداد في الإصدارات المعرضة للخطر على النحو التالي:

root@kitploit:~
private List<InCallServiceInfo> getInCallServiceComponents(UserHandle userHandle,
        String packageName, ComponentName componentName,
        int requestedType, boolean ignoreDisabled) {
        ...
        List<ResolveInfo> entries;
        entries = userPackageManager.queryIntentServices(
                serviceIntent,
                PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS);
        for (ResolveInfo entry : entries) {
            ServiceInfo serviceInfo = entry.serviceInfo;

            if (serviceInfo != null) {
                boolean isMetaFlag = serviceInfo.metaData != null &&
                        serviceInfo.metaData.getBoolean(
                                "android.telecom.CLASS_EXISTENCE_CHECK", false);
                if (isMetaFlag && !serviceClassExists(serviceInfo, userHandle)) {
                    continue;
                }
                ...
            }
        }
}

لاحظ كيف يتم تشغيل serviceClassExists ضد أي مكوّن يعلن عن نية InCallService مع قيمة بيانات وصفية android.telecom.CLASS_EXISTENCE_CHECK، وليس فقط خدمات InCallService الصالحة/المفعّلة. (يتم تنفيذ هذا الفحص في الأسفل من الفرع باستخدام getInCallServiceType و isServiceEnabled)

يتم تنفيذ serviceClassExists على النحو التالي:

root@kitploit:~
/**
 * Verifies that the class for a given ServiceInfo exists within its package.
 * This prevents a system crash if a service is declared in the manifest but its
 * class was not included in the compiled code.
 * @param serviceInfo The ServiceInfo of the service to check.
 * @param userHandle The user under which to check for the service.
 * @return {@code true} if the class exists, {@code false} otherwise.
*/
private boolean serviceClassExists(ServiceInfo serviceInfo, UserHandle userHandle) {
    Log.i(this, "serviceClassExists check");
    try {
        Context packageContext = mContext.createPackageContextAsUser(
                serviceInfo.packageName,
                Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY, userHandle);
        ClassLoader classLoader = packageContext.getClassLoader();
        Class.forName(serviceInfo.name, false, classLoader);
        return true;
    } catch (NameNotFoundException | ClassNotFoundException e) {
        Log.w(this, "Skipping InCallService: class not found for " + serviceInfo.name);
        return false;
    } catch (Exception e) {
        Log.e(this, e, "Error checking for existence of " + serviceInfo.name);
        return false;
    }
}

مخاطر createPackageContext مع CONTEXT_IGNORE_SECURITY موثقة جيدًا، وفي هذه الحالة يكون system_server نفسه هو من يقوم بتشغيلها ضد مكوّن غير موثوق فقط للتحقق من وجود فئة في DEX الخاص بذلك التطبيق. ومع ذلك، للوهلة الأولى، قد يبدو هذا آمنًا لأن السياق الذي تم الحصول عليه يُستخدم في Class.forName مع initialize=false - من المحتمل جدًا أن المطور كان على دراية بهذا الخطر وحدد هذه الوسيطة لضمان عدم تهيئة الفئة الأجنبية في system_server.

لسوء الحظ، يأتي هذا الحذر متأخرًا جدًا - الضرر قد حدث بالفعل بواسطة getClassLoader على السياق الأجنبي. إذا قام تطبيق المهاجم بتعريف AppComponentFactory في بيانه (manifest) كـ android:appComponentFactory، فإن طريقة getClassLoader على LoadedApk المطابق لسياق تم إنشاؤه باستخدام CONTEXT_INCLUDE_CODE و CONTEXT_IGNORE_SECURITY تسترجع أولاً محمّل الفئات الافتراضي للتطبيق الأجنبي من القرص ثم تشغّل كلًا من مُنشئ المصنع وطريقته instantiateClassLoader قبل إرجاع محمّل الفئات.

نظرًا لأن هذه الفئة يمكن التحكم فيها بواسطة تطبيق المهاجم، يؤدي هذا فورًا إلى تنفيذ تعليمات برمجية عشوائي في سياق Telecom.

ما بعد الاستغلال

لإظهار عواقب تنفيذ التعليمات البرمجية الناجح في system_server، يوضح إثبات المفهوم إعادة تثبيت نفسه كمكوّن ثابت (persistent) في system_server. (التقنية مقتبسة من التقنية المنشورة في إثبات مفهوم AbxOverflow / CVE-2024-34740 بواسطة Michał Bednarski)

نقوم بذلك عن طريق:

  • استرجاع كائن Signature الخاص بتطبيق إثبات المفهوم عن طريق الانعكاس الديناميكي (dynamic reflection) في PackageManagerService.mSettings مباشرة بمجرد أن نكون داخل system_server.
  • استرجاع SharedUserSetting المطابق لـ "android.uid.system" وحقن Signature الخاص بإثبات المفهوم مرتين في getSigningDetails().mPastSigningCertificates مع CertCapabilities.SHARED_USER_ID.
  • فرض إلغاء تثبيت تطبيق إثبات المفهوم وإعادة تثبيت نسخة منه مع إضافة android:sharedUserId="android.uid.system" و android:process="system" إلى البيان (manifest). (والذي يقوم أيضًا بمسح التعديل المتطاير من الخطوة السابقة إلى packages.xml)

هذا يجعل canJoinSharedUserId() في PackageSignatures ينجح بسبب تطابق سجل التدوير (rotation history)، ويؤدي إلى صلاحيات system_server ثابتة.

ملاحظة ختامية

سؤال قد يتبادر إلى ذهنك هو لماذا توجد هذه الثغرة أصلًا - على وجه الخصوص، لماذا يوجد فحص اختياري لوجود الفئة محميًا خلف علامة بيانات وصفية غير موثقة؟

من المستحيل الجزم بذلك، لكن الإجابة المحتملة على هذا اللغز يمكن العثور عليها بالتعمق قليلاً في AOSP: من المحتمل أن كلاً من فحص الفئة ومقارنة البيانات الوصفية أُضيفا لمراعاة android.net.ConnectivityCallListenerService، الذي كان قيد التنفيذ في نفس الوقت تقريبًا.

تم تعريف هذه الخدمة في البداية في بيان إطار العمل (framework manifest) ولكن لم يتم تنفيذها في أي مكان. (وعندما تمت إضافتها، كانت خلف علامة الميزة Flags.FLAG_ENABLE_INCALL_SERVICE_API) بمجرد ملاحظة الأعطال أثناء الاختبار، من المحتمل أن شخصًا ما قرر تنفيذ الإصلاح كفحص قابل لإعادة الاستخدام "للدفاع في العمق" بدلاً من استثناء مبرمج بشكل ثابت - تعريف هذه الخدمة في البيان يقول أنها تعرّف "android.telecom.CLASS_EXISTENCE_CHECK" "للإشارة إلى أن فئة هذه الخدمة قد لا تكون موجودة في جميع الإصدارات" و"لتوجيه Telecom للتحقق من وجود الفئة قبل محاولة الربط". (ولهذا السبب نحن هنا الآن)

تنزيل الأداة