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

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

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

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

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
CVE-2022-20474 — تحليل تقني مفصّل وإثبات مفهوم لثغرة CVE-2022-20474 في أندرويد، وهي ثغرة عدم تطابق في Bundle تستغل LazyValue بطول سالب لتحقيق سلوك Bundle يتغيّر ذاتيًا. | Kitploit
أدوات/GitHubGitHub/cxxsheng/cve-2022-20474
أمان أندرويدتحليل الثغرات الأمنيةالاستغلالالأوراق والأبحاثالتعلم والتعليماستغلال الملفات الثنائية
GitHubcxxsheng/cve-2022-20474

CVE-2022-20474

تحليل تقني مفصّل وإثبات مفهوم لثغرة CVE-2022-20474 في أندرويد، وهي ثغرة عدم تطابق في Bundle تستغل LazyValue بطول سالب لتحقيق سلوك Bundle يتغيّر ذاتيًا.

عرض المستودع
2014منذ سنة واحدةتمت المراجعة من قبل Kitploit

الأكثر شعبية

عرض الكل →

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

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

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

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

تحليل CVE-2022-20474 – الحزمة المتغيرة ذاتياً تحت LazyValue

مقدمة

ملاحظة مهمة، قبل قراءة هذا المقال، ينبغي أن يكون لديك فهم أولي للثغرات المتعلقة بعدم تطابق الحزمة (Bundle Mismatch). المواد المرجعية التالية إذا لم تكن قد قرأتها بعد، يُنصح بقراءتها أولاً:

  1. Bundle风水——Android序列化与反序列化不匹配漏洞详解: برنامج تعليمي كلاسيكي للمبتدئين.
  2. Android 反序列化漏洞攻防史话: مقالة تلخيصية جيدة.
  3. TheLastBundleMismatch: أول مقال حول عدم تطابق الحزمة في نمط LazyValue.

الخلفية

لقد كنت أدرس مؤخراً مقال LeakValue لـ michalbednarski بدقة. أثناء المناقشة مع Canyie، قال إن هذه المقالة تذكر أيضاً حالة حزمة متغيرة ذاتياً (Self-changing Bundle) في سيناريو LazyValue، فذهبت للبحث عن النص الأصلي، وكان هناك بالفعل مثل هذه الفقرة التي فاتتني أثناء قراءة مقال ميشال. النص الأصلي يقول ما يلي:

(Also LazyValue with negative length specified can be used (without using other bugs described in this writeup) to create self-changing Bundle, the thing LazyValue was created to eliminate. But that is another story (and separately reported to Google), in this exploit I'm aiming for more)

ما يشير إليه ميشال هو على الأرجح CVE-2022-20474 (النشرة, التصحيح)، ألقيت نظرة على التصحيح، لكن الوظيفة في رابط التصحيح لم تكن كاملة، فقمت بإكمالها ثم درستها بعناية:

@@ -4388,6 +4388,9 @@
    public Object readLazyValue(@Nullable ClassLoader loader) {
         int start = dataPosition();
         int type = readInt();
         if (isLengthPrefixed(type)) {
             int objectLength = readInt();
+            if (objectLength < 0) {
+                return null;
+            }
             int end = MathUtils.addOrThrow(dataPosition(), objectLength);
             int valueLength = end - start;
             setDataPosition(end);
             return new LazyValue(this, start, valueLength, type, loader);
         } else {
            return readValue(type, loader, /* clazz */ null);
         }
    }             

objectLength في الكود هو length في LazyValue، في الحقيقة هذا هو فقط طول الكائن المتغير المضمن في LazyValue، بينما يجب أن يكون طول LazyValue بأكمله محكوماً بحقل mLength، أي valueLength في الكود، والذي يُمرر إلى mLength في دالة البناء لـ LazyValue.

دعنا نقارن مع تنسيق تخطيط LazyValue كما يلي:

       /**
         *                      |   4B   |   4B   |
         * mSource = Parcel{... |  type  | length | object | ...}
         *                      a        b        c        d
         * length = d - c
         * mPosition = a
         * mLength = d - a
         */

بناءً على ما سبق، يمكننا الحصول على الحقائق التالية:

  1. mLength يمثل طول LazyValue بالكامل، mLength = objectLength + 8 بايت.
  2. يجب أن يكون objectLength أكبر من أو يساوي 0.
  3. كائن LazyValue يحفظ فقط mLength، ولا يحفظ objectLength، لأن LazyValue يقوم بنسخ الذاكرة بناءً على الكائن بالكامل.
  4. المؤشر بعد القراءة يتحرك للأمام، وقد تتم قراءة LazyValue مرة أخرى.

ثم، بعد تفكير عميق، اتفقنا جميعاً على أن هذه الحقائق لا فائدة منها! لأنه بناءً على الحقائق أعلاه، لا يمكن تعديل القيمة إلا مرة واحدة أثناء القراءة، ونحن نعلم أن الفكرة الأساسية لـ Self-changed Bundle هي التعديل بعد اكتمال القراءة لتجاوز الفحوصات الأمنية.

بينما كنا على وشك الاستسلام، اكتشفنا فجأة بعض التفاصيل في وصف التصحيح:

Addresses a security vulnerability where a (-8) length object would cause dataPosition to be reset back to the statt of the value, and be re-read again.

objectLength غير الطبيعي

حيث يذكر أن objectLength يساوي -8 يسبب بعض المشاكل، مما أعطانا إلهاماً إضافياً. هل يمكن لـ LazyValue العمل بشكل طبيعي في هذه الحالة؟

        @Override
        public Object apply(@Nullable Class<?> clazz, @Nullable Class<?>[] itemTypes) {
            Parcel source = mSource;
            if (source != null) {
                synchronized (source) {
                    // Check mSource != null guarantees callers won't ever see different objects.
                    if (mSource != null) {
                        int restore = source.dataPosition();
                        try {
                            source.setDataPosition(mPosition);
                            mObject = source.readValue(mLoader, clazz, itemTypes);
                        } finally {
                            source.setDataPosition(restore);
                        }
                        mSource = null;
                    }
                }
            }
            return mObject;
        }

  	/**
     * @see #readValue(int, ClassLoader, Class, Class[])
     */
    @Nullable
    private <T> T readValue(@Nullable ClassLoader loader, @Nullable Class<T> clazz,
            @Nullable Class<?>... itemTypes) {
        int type = readInt();
        final T object;
        if (isLengthPrefixed(type)) {
            int length = readInt();
            int start = dataPosition();
            object = readValue(type, loader, clazz, itemTypes);
            int actual = dataPosition() - start;
            if (actual != length) {
                Slog.wtfStack(TAG,
                        "Unparcelling of " + object + " of type " + Parcel.valueTypeToString(type)
                                + "  consumed " + actual + " bytes, but " + length + " expected.");
            }
        } else {
            object = readValue(type, loader, clazz, itemTypes);
        }
        return object;
    }

يمكن ملاحظة أن readValue الفعلية تبدأ القراءة من mPosition، ثم تقرأ LazyType و objectLength بالتتابع، ثم تدخل في تدفق قراءة Value العادي، مثل قراءة ClassName لـ Parcelable، ثم تنفيذ createFromParcel، بعد اكتمال القراءة، لا يوجد فرق عن Key-Value العادي، ولا يؤثر على التسلسل اللاحق. بالرجوع مرة أخرى، الفكرة الأساسية لـ Self-changed Bundle هي التعديل بعد اكتمال القراءة، ولكن هذا مجرد قراءة عادية تتجاوز الحدود، ويبدو أن هذا الاتجاه غير قابل للتطبيق.

ماذا لو لم يتم تطبيق LazyValue خلال هذه العملية، أي أنه يستمر في المشاركة في IPC كهوية LazyValue، وعندها سيتم استدعاء دالة writeToParcel:

     public void writeToParcel(Parcel out) {
            Parcel source = mSource;
            if (source != null) {
                synchronized (source) {
                    if (mSource != null) {
                        out.appendFrom(source, mPosition, mLength);
                        return;
                    }
                }
            }
            out.writeValue(mObject);
        }

سيتم نسخ LazyValue بالكامل مباشرة، إلا إذا كان mLength = 0. انتظر! ذكر أعلاه أن mLength = objectLength + 8 بايت، ومن معلومات التصحيح يمكننا معرفة أنه لتفعيل الثغرة يجب أن يكون objectLength = -8، وبالتالي mLength = 0 يتحقق. بمعنى آخر، في هذا السيناريو تختفي LazyValue بالكامل، ولن يتم نسخ سوى String Key، مما يسبب كتابة ناقصة، وبذلك يتحقق شرط Self-changed Bundle مباشرة.

بعد معرفة السبب، يمكننا البدء في إعادة إنتاج الثغرة، ولكن قبل ذلك، نحتاج إلى تفاصيل صغيرة إضافية.

التفاصيل 1: نوعان من الحزم (Bundle)

 static final int BUNDLE_MAGIC = 0x4C444E42; // 'B' 'N' 'D' 'L'
 private static final int BUNDLE_MAGIC_NATIVE = 0x4C444E44; // 'B' 'N' 'D' 'N'

نعلم جميعاً أن تخطيط الذاكرة لـ Bundle تقريباً كما يلي:

       /**
         *        |   4B   |   4B   |   4B   |
         * Bundle{| length |  MAGIC |  size  | Key  | Value | Key  | Value | ...}
         *
         */

MAGIC هو رقم سحري في تخطيط الذاكرة لـ Bundle، يمكن أن يكون BUNDLE_MAGIC أو BUNDLE_MAGIC_NATIVE. الفرق الأهم بينهما هو أن BUNDLE_MAGIC يؤدي إلى إعادة ترتيب Key-Value بعد اكتمال إلغاء التسلسل، كما في الكود التالي:

تنزيل الأداة