BroadcastTest
背景
逆向APK可知程序中仅有MainActivity$Message和三个Receiver类。
前者实现了一个Parcelable类,后三个则是广播。
其中Receiver1是exported的,接收并向Receiver2发送广播,Receiver2和3则非exported,只能接收内部发送的广播。
功能为Receiver1接收base64传入的data,然后将其反序列化得到一个Bundle,再广播给Receiver2。
Receiver2检查Bundle中“command”存在且值非"getflag",然后再次发送广播给Receiver3。
Receiver3检查Bundle中"command"存在且值为"getflag",通过则回显正确。
简单搜索可以找到这篇文章,描述了Parcel中对于读出和写入时类型不一致会产生的漏洞。
原理
Android提供了独有的Parcelable接口来实现序列化的方法,只要实现这个接口,一个类的对象就可以实现序列化并可以通过Intent或Binder传输。
其中,关键的writeToParcel和readFromParcel方法,分别调用Parcel类中的一系列write方法和read方法实现序列化和反序列化。
可序列化的Parcelable对象一般不单独进行序列化传输,需要通过Bundle对象携带。 Bundle的内部实现实际是Hashmap,以Key-Value键值对的形式存储数据。例如, Android中进程间通信频繁使用的Intent对象中可携带一个Bundle对象,利用putExtra(key, value)方法,可以往Intent的Bundle对象中添加键值对(Key Value)。Key为String类型,而Value则可以为各种数据类型,包括int、Boolean、String和Parcelable对象等等,Parcel类中维护着这些类型信息。
// Keep in sync with frameworks/native/include/private/binder/ParcelValTypes.h.
private static final int VAL_NULL = -1;
private static final int VAL_STRING = 0;
private static final int VAL_INTEGER = 1;
private static final int VAL_MAP = 2;
private static final int VAL_BUNDLE = 3;
private static final int VAL_PARCELABLE = 4;
private static final int VAL_SHORT = 5;
private static final int VAL_LONG = 6;
private static final int VAL_FLOAT = 7;
对Bundle进行序列化时,依次写入携带所有数据的长度、Bundle魔数(0x4C444E42)和键值对。见BaseBundle.writeToParcelInner方法
int lengthPos = parcel.dataPosition();
parcel.writeInt(-1); // dummy, will hold length
parcel.writeInt(BUNDLE_MAGIC);
int startPos = parcel.dataPosition();
parcel.writeArrayMapInternal(map);
int endPos = parcel.dataPosition();
// Backpatch length
parcel.setDataPosition(lengthPos);
int length = endPos - startPos;
parcel.writeInt(length);
parcel.setDataPosition(endPos);
pacel.writeArrayMapInternal方法写入键值对,先写入Hashmap的个数,然后依次写入键和值
/**
* Flatten an ArrayMap into the parcel at the current dataPosition(),
* growing dataCapacity() if needed. The Map keys must be String objects.
*/
/* package */ void writeArrayMapInternal(ArrayMap<String, Object> val) {
...
final int N = val.size();
writeInt(N);
...
int startPos;
for