Skip to content
KitploitKITPLOIT
أدواتعمليات الاستغلالالمدونة
Log in
إرسال
أدواتعمليات الاستغلالالمدونة
إرسال

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

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

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

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
ThisSeemsWrong — شرح واستغلال ثغرة CVE-2024-49746: إغلاق Parcel::continueWrite في أندرويد لـ File Descriptors التي تُستخدم لاحقًا | Kitploit
أدوات/GitHubGitHub/michalbednarski/thisseemswrong
أمان أندرويدأطر الاستغلالتحليل الثغرات الأمنيةجمع المعلوماتتطوير الحمولاتاستغلال الملفات الثنائية
GitHubmichalbednarski/thisseemswrong

ThisSeemsWrong

شرح واستغلال ثغرة CVE-2024-49746: إغلاق Parcel::continueWrite في أندرويد لـ File Descriptors التي تُستخدم لاحقًا

عرض المستودع
47156منذ 11 أشهرتمت المراجعة من قبل Kitploit

الأكثر شعبية

عرض الكل →

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

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

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

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

ظهر إصلاح لهذه المشكلة تحت الرقم CVE-2024-49746: bulletin, patch

"يبدو هذا خطأ"

العنوان أعلاه هو التعليق من طريقة Parcel::continueWrite، وهي المسؤولة فعليًا عن تغيير حجم كائنات Parcel، سواء عندما يطلب المستخدم ذلك صراحةً (على سبيل المثال من خلال setDataSize()) أو عند استدعاء إحدى طرق write عندما تكون سعة البيانات الحالية صغيرة جدًا.```cpp status_t Parcel::continueWrite(size_t desired) { // SNIP: Validate desired size // SNIP: Assign kernelFields & rpcFields from variant member of this class // SNIP: Count number of objects (Binder handles and File Descriptors) // that will be present after resize and assign to objectsSize

if (mOwner) {
    // If the size is going to zero, just release the owner's data.
    if (desired == 0) {
        freeData();
        return NO_ERROR;
    }

    // If there is a different owner, we need to take
    // posession.
    uint8_t* data = (uint8_t*)malloc(desired);
    // SNIP: Check if malloc succeeded
    binder_size_t* objects = nullptr;

    if (kernelFields && objectsSize) {
        objects = (binder_size_t*)calloc(objectsSize, sizeof(binder_size_t));
        // SNIP: Check if calloc succeeded

        // Little hack to only acquire references on objects
        // we will be keeping.
        size_t oldObjectsSize = kernelFields->mObjectsSize;
        kernelFields->mObjectsSize = objectsSize;
        acquireObjects();
        kernelFields->mObjectsSize = oldObjectsSize;
    }
    // SNIP: rpcFields handling for non-/dev/binder Parcels

    if (mData) {
        memcpy(data, mData, mDataSize < desired ? mDataSize : desired);
    }
    if (objects && kernelFields && kernelFields->mObjects) {
        memcpy(objects, kernelFields->mObjects, objectsSize * sizeof(binder_size_t));
    }
    // ALOGI("Freeing data ref of %p (pid=%d)", this, getpid());
    if (kernelFields) {
        // TODO(b/239222407): This seems wrong. We should only free FDs when
        // they are in a truncated section of the parcel.
        closeFileDescriptors();
    }
    mOwner(mData, mDataSize, kernelFields ? kernelFields->mObjects : nullptr,
           kernelFields ? kernelFields->mObjectsSize : 0);
    mOwner = nullptr;

    // SNIP: Allocation count tracking
    // SNIP: Assign data and objects to this object
} else if (mData) {
    // SNIP: Resize data owned by this instance of Parcel
} else {
    // SNIP: Allocate initial data for currently empty Parcel
}

return NO_ERROR;

}

[عندما تم تقديم هذا التعليق](https://android.googlesource.com/platform/frameworks/native/+/53b6ffe5af3951e8784c451ef8c4ff19f3d6b196%5E!/)، تم نقل استدعاء `closeFileDescriptors()` من `IPCThreadState::freeBuffer()` (الذي يتم استدعاؤه في الكود أعلاه عبر مؤشر الدالة `mOwner()`) إلى طريقة `continueWrite()`، لكن المنطق بقي كما كان من قبل. بعد كل شيء، `Parcel` هو جزء أساسي من IPC في أندرويد، وإذا كان IPC الأساسي يغلق واصفات الملفات التي لا ينبغي له إغلاقها، فستكون مشكلة واضحة.

مما يقودنا إلى الجزء المهم: متى يتم استخدام الكود أعلاه؟ يتم استخدامه عندما تقوم فئة `Parcel` بنقل ملكية البيانات المستلمة من برنامج تشغيل Binder (والتي توجد في ذلك الوقت في [`/dev/binder` `mmap`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/libs/binder/ProcessState.cpp;l=587-592;drc=187efe18e3de6258af0230198c881915cc695567) ولا يمكن الكتابة عليها (أي محاولة لكتابة تلك الذاكرة ستؤدي إلى `SIGSEGV`))، أي أن `Parcel` إما أن تكون بيانات معاملة واردة (وسيطة `data` التي تم تمريرها إلى [`onTransact()`](https://developer.android.com/reference/android/os/Binder#onTransact(int,%20android.os.Parcel,%20android.os.Parcel,%20int))) أو رد وارد (أي كائن `Parcel` الذي تم تمريره إلى استدعاء [`transact()`](https://developer.android.com/reference/android/os/IBinder#transact(int,%20android.os.Parcel,%20android.os.Parcel,%20int)) كوسيطة `reply`، حيث يقوم `transact()` بتعيين مرجع داخل كائن `Parcel` ذلك)

من الناحية العملية، الحالة الوحيدة التي قد ندخل فيها كتلة `if (mOwner)` هي عندما [يستدعي النظام `setDataSize(0)` لتحرير بيانات المعاملة](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/native/libs/binder/IPCThreadState.cpp;l=1483-1488;drc=187efe18e3de6258af0230198c881915cc695567)، ولكن في هذه الحالة سندخل أيضًا `if (desired == 0)` الذي يقوم بالإرجاع المبكر. أثناء الاستخدام الشرعي للنظام، لا توجد حالة ندخل فيها مسار "إذا كان هناك مالك مختلف، نحتاج إلى الاستيلاء على الملكية"

# تفعيل مسار "الاستيلاء على الملكية"

في أحد استغلالاتي السابقة، أوضحت [حالة حيث يمكن لـ `createFromParcel()` في الواقع استدعاء `writeInt(0)` على `Parcel` الذي يفترض أن تقرأ منه](https://github.com/michalbednarski/TheLastBundleMismatch#side-effects). بينما منع الإصلاح هناك تنفيذ أي طرق `createFromParcel()` غير `Intent` داخل `AccountManagerService`، إلا أن مسار `createFromParcel()` إلى `writeInt(0)` بقي محفوظًا.

للتلخيص، [داخل `PackageParser` لدينا الكود التالي](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/content/pm/PackageParser.java;l=7789-7795;drc=7d3ffbae618e9e728644a96647ed709bf39ae759):```java
final Class<T> cls = (Class<T>) Class.forName(componentName);
final Constructor<T> cons = cls.getConstructor(Parcel.class);

intentsList = new ArrayList<>(N);
for (int i = 0; i < N; ++i) {
    intentsList.add(cons.newInstance(in));
}

لذا، يمكننا أن يكون لدينا كائن Parcel الذي تم تمريره إلى createFromParcel مُمررًا إلى أي مُنشئ public متوفر في النظام يقبل وسيطة Parcel واحدة

وفي مكان آخر لدينا الكود التالي:```java public PooledStringWriter(Parcel out) { mOut = out; mPool = new HashMap<>(); mStart = out.dataPosition(); out.writeInt(0); // reserve space for final pool size. }

لذا، لتفعيل مسار "الاستحواذ"، نحتاج إلى استدعاء `readParcelable` بشكل اعتباطي على `Parcel` الذي تم تمريره إلى `onTransact()` كـ `data`. في هذا الاستغلال، أستخدم لذلك [نفس المسار الذي استخدمته سابقًا في مسار آخر](https://github.com/michalbednarski/LeakValue#putting-parcelables-in-system_server-and-retrieving-them). لدي استدعاء `readParcelable` لـ `PackageParser$Activity.CREATOR.createFromParcel()`، والذي بدوره يقرأ اسم `PooledStringWriter` ويستدعي الباني الخاص به، وبعد ذلك تنتهي بيانات `Parcel`، لذا فإن `writeInt()` تحتاج إلى إعادة تخصيص `Parcel`، مما يدخل في مسار "الاستحواذ" الخاص بنا.

جدير بالذكر هنا، إذا لم تكن هناك نهاية لبيانات `Parcel` في تلك النقطة، فإن `writeInt()` ستحاول الكتابة فوق البيانات في مكانها، مما يؤدي في حالة البيانات المدعومة بـ `/dev/binder` `mmap` إلى حدوث `SIGSEGV`.

# مُعقّم واصفات الملفات

كانت فكرتي الأولية هي أن يقوم مسار "الاستحواذ" بإغلاق واصفات الملفات، وبعد ذلك في نهاية المعاملة سيتم إغلاق نفس الواصفات مرة أخرى، ولكن بين تلك الأحداث، سأضع واصف ملف آخر داخل `system_server` في معاملة أخرى، وفي وقت لاحق سأستعيد واصف الملف الخاص بي، حيث أن واصف الملف (FD) هذا يشير في تلك النقطة إلى ملف مختلف.

نجح هذا الأمر على جهاز المحاكاة الخاص بي باستخدام إصدار قديم من AOSP، ولكن بمجرد أن جربت مع إصدار أحدث، تم إيقاف هذه الخطة بواسطة [مُعقّم واصفات الملفات (FDSan)](https://android.googlesource.com/platform/bionic/+/refs/heads/main/docs/fdsan.md)
تنزيل الأداة