EvilParcel漏洞分析

本文深入探讨了EvilParcel漏洞,一种在Android系统中利用Parcelable接口的错误,导致恶意软件获得高权限,能够独立安装和卸载应用。文章分析了漏洞原理,展示了如何通过创建特殊构造的Bundle对象绕过安全检查,以及Google对此类漏洞的修复措施。
摘要由CSDN通过智能技术生成

介绍 (Introduction)

In mid-April, we published news about the

4月中旬,我们发布了有关

Android.InfectionAds.1 (Android.InfectionAds.1)

trojan, which exploited several critical vulnerabilities in Android. One of them, CVE-2017-13156 (also known as

木马,它利用了Android中的几个关键漏洞。 其中之一CVE-2017-13156(也称为

Janus), allows malware to infect APK files without damaging the digital signature. The other one is CVE-2017-13315. It gives the trojan extended privileges, so that it can install and uninstall applications independently from user. A detailed analysis of Janus ),允许恶意软件感染APK文件而不会损坏数字签名。 另一个是CVE-2017-13315。 它赋予木马扩展权限,因此它可以独立于用户安装和卸载应用程序。 详细分析

Android.InfectionAds.1 (Android.InfectionAds.1)

is available in

可用于

our virus library; while we’re here we will touch upon the CVE-2017-13315 vulnerability and see what it does. 我们的病毒库 ; 在这里,我们将介绍CVE-2017-13315漏洞并查看其作用。

CVE-2017-13315 belongs to the group of vulnerabilities dubbed EvilParcel. They are found in various Android system classes. Errors in these classes make it possible to substitute information during the data exchange between apps and the system. Malware that exploit the EvilParcel vulnerabilities are thus granted higher privileges and become capable of the following:

CVE-2017-13315属于被称为EvilParcel的漏洞。 它们可以在各种Android系统类中找到。 这些类中的错误使在应用程序与系统之间的数据交换期间替换信息成为可能。 因此,利用EvilParcel漏洞的恶意软件将被授予更高的特权,并具有以下功能:

  • installing and removing applications with any permissions without confirmation from users;

    未经用户确认而安装和删除具有任何权限的应用程序;
  • infecting software installed on the device and replacing clean originals with infected copies when used together with other vulnerabilities;

    感染设备上安装的软件,并与其他漏洞一起使用时,将干净的原件替换为受感染的副本;
  • resetting the lock screen PIN on Android devices.

    在Android设备上重置锁定屏幕PIN。

As of now, we know about 7 vulnerabilities of this type:

截至目前,我们知道此类型的7个漏洞:

  • CVE-2017-0806 (error in the GateKeeperResponse class), published in October 2017;

    CVE-2017-0806(GateKeeperResponse类中的错误),于2017年10月发布;
  • CVE-2017-13286 (error in the OutputConfiguration class, published in April 2018;

    CVE-2017-13286(OutputConfiguration类中的错误,于2018年4月发布;
  • CVE-2017-13287 (error in the VerifyCredentialResponse class), published in April 2018;

    CVE-2017-13287(VerifyCredentialResponse类中的错误),于2018年4月发布;
  • CVE-2017-13288 (error in the PeriodicAdvertizingReport class), published in April 2018;

    CVE-2017-13288(PeriodicAdvertizingReport类中的错误),于2018年4月发布;
  • CVE-2017-13289 (error in the ParcelableRttResults class), published in April 2018;

    CVE-2017-13289(ParcelableRttResults类中的错误),于2018年4月发布;
  • CVE-2017-13311 (error in the SparseMappingTable class), published in May 2018;

    CVE-2017-13311(SparseMappingTable类中的错误),于2018年5月发布;
  • CVE-2017-13315 (error in the DcParamObject class), published in May 2018.

    CVE-2017-13315(DcParamObject类错误),于2018年5月发布。

All of them pose a threat to devices running Android 5.0 — 8.1 with no May 2018 (or later) security update installed.

所有这些都对没有安装2018年5月(或更高版本)安全更新的运行Android 5.0-8.1的设备构成威胁。

EvilParcel漏洞的前提条件 (Prerequisites for EvilParcel vulnerabilities)

Let's see how EvilParcel vulnerabilities can appear. First of all, we need to look into some features of the Android applications. All Android programs interact with each other, as well as with the operating system, by sending and receiving Intent objects. These objects can contain an arbitrary number of key-value pairs inside a Bundle object.

让我们看看EvilParcel漏洞如何出现。 首先,我们需要研究Android应用程序的某些功能。 通过发送和接收Intent对象,所有Android程序之间以及与操作系统之间都进行交互。 这些对象可以在Bundle对象中包含任意数量的键/值对。

When transferring an Intent, a Bundle object is converted (serialized) into a byte array wrapped in Parcel, and then automatically deserialized after reading keys and values from a serialized Bundle.

传输Intent时,将Bundle对象转换(序列化)为包裹在Parcel中的字节数组,然后在从序列化Bundle中读取键和值后自动将其反序列化。

In the Bundle, the key is the string, and the value can be almost anything. For instance, it can be a primitive type, a string, or a container with primitive types or strings. It can also be a Parcelable object.

在Bundle中,键是字符串,值几乎可以是任何值。 例如,它可以是原始类型,字符串或具有原始类型或字符串的容器。 它也可以是一个Parcelable对象。

Thus, the Bundle can contain an object of any type that implements the Parcelable interface. For this, we need to implement the writeToParcel() and createFromParcel() methods to serialize and deserialize the object.

因此,捆绑包可以包含实现Parcelable接口的任何类型的对象。 为此,我们需要实现writeToParcel()和createFromParcel()方法来序列化和反序列化对象。

To illustrate our point, let’s create a simple serialized Bundle. We’ll write a code that puts three key-value pairs in the Bundle and serializes it:

为了说明我们的观点,让我们创建一个简单的序列化Bundle。 我们将编写一个代码,将三个键值对放入Bundle并将其序列化:

Figure 1. Structure of a serialized Bundle object

图1.序列化的Bundle对象的结构

Note the specific features of Bundle serialization:

请注意Bundle序列化的特定功能:

  • all key-value pairs are written sequentially;

    所有键值对均按顺序写入;
  • the value type is indicated before each value (13 for the byte array, 1 for the integer, 0 for the string, etc.);

    在每个值之前指示值类型(13表示字节数组,1表示整数,0表示字符串,等等);
  • variable length data size is indicated before the data (length for the string, number of bytes for the array);

    可变长度数据大小在数据之前指示(字符串的长度,数组的字节数);
  • all values are 4-byte aligned.

    所有值都是4字节对齐的。

All keys and values are written in the Bundle sequentially so that when accessing any key or value of a serialized Bundle object, the latter deserializes entirely, also initializing all contained Parcelable objects.

所有键和值都按顺序写入Bundle,以便在访问序列化Bundle对象的任何键或值时,后者将完全反序列化,还初始化所有包含的Parcelable对象。

So, what could be the problem? The problem is that some system classes that implement Parcelable may contain errors in the createFromParcel() and writeToParcel() methods. In these classes, the number of bytes read in createFromParcel() will differ from the number of bytes written in writeToParcel(). If you place an object of this class inside a Bundle, the object boundaries inside the Bundle will change after reserialization. This creates the conditions for exploiting an EvilParcel vulnerability.

那么,可能是什么问题呢? 问题在于,实现Parcelable的某些系统类可能在createFromParcel()和writeToParcel()方法中包含错误。 在这些类中,在createFromParcel()中读取的字节数将与在writeToParcel()中写入的字节数不同。 如果将此类的对象放置在Bundle中,则重新序列化后,Bundle中的对象边界将更改。 这为利用EvilParcel漏洞创造了条件。

Let’s see an example of a class that contains this error:

让我们看一个包含此错误的类的示例:

class Demo implements Parcelable {
    byte[] data;
    public Demo() {
      this.data = new byte[0];
    }
    protected Demo(Parcel in) {
      int length = in.readInt();
      data = new byte[length];
      if (length > 0) {
         in.readByteArray(data);
      }
     }
     public static final Creator<Demo> CREATOR = new Creator<Demo>() {
       @Override
       public Demo createFromParcel(Parcel in) {
         return new Demo(in);
       }
     };
     @Override
     public void writeToParcel(Parcel parcel, int i) {
       parcel.writeInt(data.length);
       parcel.writeByteArray(data);
     }
}

If the data array size is 0, then when creating an object, one int (4 bytes) will be read in createFromParcel() and two int (8 bytes) will be written in writeToParcel(). The first int will be written by explicitly calling writeInt. The second int will be written when calling writeByteArray(), since the array length is always written before the array in Parcel (see Figure 1).

如果数据数组大小为0,则在创建对象时,将在createFromParcel()中读取一个int(4个字节),并在writeToParcel()中写入两个int(8字节)。 第一个int将通过显式调用writeInt写入。 调用writeByteArray()时将写入第二个int,因为数组长度始终写在Parcel中的数组之前(请参见图1)。

Situations where the data array size equals 0 are pretty rare. But even when this happens, the program keeps operating, if you only transmit one serialized object at a time (in our example, the Demo object). Therefore, such errors tend to go unnoticed.

数据数组大小等于0的情况非常少见。 但是即使发生这种情况,如果一次仅传输一个序列化对象(在我们的示例中为Demo对象),程序仍将继续运行。 因此,这种错误往往不会引起注意。

Now we’ll try to place a Demo object with zero array length in the Bundle:

现在,我们将尝试在Bundle中放置一个数组长度为零的Demo对象:

Figure 2. The result of adding a zero-length Demo object to the Bundle

图2.将零长度的Demo对象添加到Bundle的结果

We serialize the object:

我们序列化对象:

Figure 3. The Bundle object after serialization

图3.序列化后的Bundle对象

Now let’s try to deserialize it:

现在让我们尝试反序列化:

Figure 4. The Bundle object after deserialization

图4.反序列化后的Bundle对象

What do we get? Let’s have a look at the Parcel fragment:

我们得到什么? 让我们看一下包裹片段:

Figure 5. Parcel structure after Bundle deserialization

图5. Bundle反序列化后的包裹结构

In Figures 4 and 5, we see that instead of two int, one int was read in the createFromParcel method during deserialization. Therefore, all subsequent values from the Bundle were read incorrectly. The 0x0 value at 0x60 was read as the length of the next key. The 0x1 value at 0x64 was read as a key. The 0x31 value at 0x68 was read as a value type. Parcel has no values with type 0x31, therefore readFromParcel() painstakingly reports an exception.

在图4和5中,我们看到在反序列化期间,在createFromParcel方法中读取了一个int,而不是两个int。 因此,从捆绑包中读取所有后续值不正确。 读取0x60处的0x0值作为下一个键的长度。 读取0x64处的0x1值作为键。 读取0x68处的0x31值作为值类型。 包裹没有类型0x31的值,因此readFromParcel()精心报告了一个异常。

How can this be used in real life and become a vulnerability? Let’s see! The above error in the Parcelable system classes enables the creation of Bundles that may differ during the first and repeated deserializations. To demonstrate this, we’ll modify the previous example:

如何在现实生活中使用它并使其成为漏洞? 让我们来看看! Parcelable系统类中的上述错误使创建Bundle的过程可能在第一次反序列化和重复反序列化期间有所不同。 为了说明这一点,我们将修改前面的示例:

Parcel data = Parcel.obtain();
data.writeInt(3); // 3 entries
data.writeString("vuln_class");
data.writeInt(4); // value is Parcelable
data.writeString("com.drweb.testbundlemismatch.Demo");
data.writeInt(0); // data.length
data.writeInt(1); // key length -> key value
data.writeInt(6); // key value -> value is long
data.writeInt(0xD); // value is bytearray -> low(long)
data.writeInt(-1); // bytearray length dummy -> high(long)
int startPos = data.dataPosition();
data.writeString("hidden"); // bytearray data -> hidden key
data.writeInt(0); // value is string
data.writeString("Hi there"); // hidden value
int endPos = data.dataPosition();
int triggerLen = endPos - startPos;
data.setDataPosition(startPos - 4);
data.writeInt(triggerLen); // overwrite dummy value with the real value
data.setDataPosition(endPos);
data.writeString("A padding");
data.writeInt(0); // value is string
data.writeString("to match pair count");
int length = data.dataSize();
Parcel bndl = Parcel.obtain();
bndl.writeInt(length);
bndl.writeInt(0x4C444E42); // bundle magic
bndl.appendFrom(data, 0, length);
bndl.setDataPosition(0);

This code creates a serialized Bundle that contains a vulnerable class. Now let’s see what we get after executing this code:

此代码创建一个包含易受攻击类的序列化Bundle。 现在让我们看看执行以下代码后得到的结果:

Figure 6. Creating a Bundle with a vulnerable class

图6.使用一个易受攻击的类创建一个Bundle

After the first deserialization, this Bundle will contain the following keys:

第一次反序列化之后,此捆绑包将包含以下密钥:

Figure 7. After deserialization of a Bundle with a vulnerable class

图7.具有弱类的Bundle反序列化之后

Now we will serialize the Bundle again, then deserialize it again, and look at the list of keys:

现在,我们将再次序列化Bundle,然后再次对其进行反序列化,然后查看密钥列表:

Figure 8. Result of reserializing and deserializing of a Bundle with a vulnerable class

图8.对具有弱类的Bundle重新序列化和反序列化的结果

What do we see? The Bundle now contains the Hidden key (with the string value «Hi there!»), which was not there before. Let us look into the Parcel fragment of this Bundle to see why this happened:

我们看到了什么? 捆绑包中现在包含以前没有的Hidden键(字符串值“ Hi there!”)。 让我们看一下这个Bundle的Parcel片段,看看发生这种情况的原因:

Figure 9. Parcel structure of a Bundle object with a vulnerable class after two serialization and deserialization cycles

图9.经过两个序列化和反序列化循环后,具有脆弱类的Bundle对象的包裹结构

This is where we can see the whole point of EvilParcel vulnerabilities. We can specifically create a Bundle that will contain a vulnerable class. Changing the boundaries of this class will allow the placement of any object in this Bundle; for example, an Intent, which will only appear in the Bundle after the second deserialization. This helps to hide an Intent from the OS security mechanisms.

在这里我们可以看到EvilParcel漏洞的全部内容。 我们可以专门创建一个包含易受攻击的类的捆绑包。 更改此类的边界将允许在此Bundle中放置任何对象; 例如,一个Intent,它只会在第二次反序列化之后出现在Bundle中。 这有助于在OS安全机制中隐藏Intent。

利用EvilParcel (Exploiting EvilParcel)

Android.InfectionAds.1 (Android.InfectionAds.1)

exploited CVE-2017-13315 to install and remove software independently from device owners. But how?

利用CVE-2017-13315独立于设备所有者安装和删除软件。 但是如何?

In 2013, the error 7699048, also known as Launch AnyWhere, was discovered. It allowed third-party applications to start arbitrary activities on behalf of a more privileged system user. See the diagram below for the action mechanism:

2013年,发现错误7699048 ,也称为Launch AnyWhere。 它允许第三方应用程序代表特权更大的系统用户启动任意活动。 有关动作机制,请参见下图:

Figure 10. Operation of the error 7699048

图10.错误7699048的操作

An exploiting application can use this vulnerability to implement the AccountAuthenticator service, designed to add new accounts to the operating system. The error 7699048 helps the exploit launch activities to install, remove, replace applications, as well as reset the PIN or Pattern Lock and cause much more trouble.

被利用的应用程序可以利用此漏洞来实现AccountAuthenticator服务,该服务旨在将新帐户添加到操作系统。 错误7699048可以帮助漏洞利用程序启动活动来安装,删除,替换应用程序,以及重置PIN或Pattern Lock并造成更多的麻烦。

Google Inc. has eliminated this breach by prohibiting the launch of arbitrary activity from AccountManager. Now, AccountManager only allows the launch of activities originating from the same application. For this purpose, it checks and matches the digital signature of software that initiated the activity with the signature of the application where the activity is located. It looks like this:

Google Inc.通过禁止从AccountManager启动任意活动来消除此违规行为。 现在,AccountManager仅允许启动源自同一应用程序的活动。 为此,它检查启动活动的软件的数字签名,并将其与活动所在的应用程序的签名进行匹配。 看起来像这样:

if (result != null
   && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
   /*
    * The Authenticator API allows third party authenticators to
    * supply arbitrary intents to other apps that they can run,
    * this can be very bad when those apps are in the system like
    * the System Settings.
    */
   int authenticatorUid = Binder.getCallingUid();
   long bid = Binder.clearCallingIdentity();
   try {
     PackageManager pm = mContext.getPackageManager();
     ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId);
     int targetUid = resolveInfo.activityInfo.applicationInfo.uid;
     if (PackageManager.SIGNATURE_MATCH !=
           pm.checkSignatures(authenticatorUid, targetUid)) {
          throw new SecurityException(
              "Activity to be started with KEY_INTENT must " +
              "share Authenticator's signatures");
     }
   } finally {
     Binder.restoreCallingIdentity(bid);
   }
}

It would seem that the problem has been solved, but it’s not as easy as all that. It turned out that the well-known vulnerability, EvilParcel CVE-2017-13315, provides a workaround! As we already know, after fixing Launch AnyWhere, the system verifies the digital signature of the application. If it is successfully verified, the Bundle is transferred to IAccountManagerResponse.onResult(). At the same time, onResult() is called via the IPC mechanism, so the Bundle is serialized again. While implementing onResult(), the following happens:

看来问题已经解决了,但并非如此简单。 事实证明,众所周知的漏洞EvilParcel CVE-2017-13315提供了一种解决方法! 众所周知,在修复了Launch AnyWhere之后,系统会验证应用程序的数字签名。 如果成功验证,则捆绑包将传输到IAccountManagerResponse.onResult()。 同时,通过IPC机制调用onResult(),因此再次对Bundle进行序列化。 在实现onResult()时,会发生以下情况:

/** Handles the responses from the AccountManager */
private class Response extends IAccountManagerResponse.Stub {
    public void onResult(Bundle bundle) {
      Intent intent = bundle.getParcelable(KEY_INTENT);
      if (intent != null && mActivity != null) {
        // since the user provided an Activity we will silently start intents
        // that we see
        mActivity.startActivity(intent);
        // leave the Future running to wait for the real response to this request
      }
      //<.....>
     }
     //<.....>
}

Then, the Bundle extracts the intent key, and the activity is launched without any verification.

然后,捆绑软件提取意图密钥,并且无需任何验证即可启动活动。

Thus, to launch an arbitrary activity with system privileges, you only need to create a Bundle with the Intent field hidden upon the first deserialization and appearing during the repeated deserialization.

因此,要启动具有系统特权的任意活动,只需创建一个Bundle,其Intent字段将在第一次反序列化时隐藏,并在重复的反序列化期间出现。

As we already know, EvilParcel vulnerabilities can actually perform this task.

众所周知,EvilParcel漏洞实际上可以执行此任务。

At the moment, all known vulnerabilities of this type have been fixed within the vulnerable Parcelable classes. However, new vulnerable classes may appear in the future. Bundle implementation and the mechanism for adding new accounts are still the same as before. They still allow us to create this exact exploit when detecting old or new vulnerable Parcelable classes. Moreover, these classes are still implemented manually, and the programmer has to make sure that the length of the serialized Parcelable object stays the same, which is a human factor with all that it implies. However, we hope there will be as few such errors as possible, and the EvilParcel vulnerabilities will not be a threat to Android users.

目前,所有此类漏洞已在易受攻击的Parcelable类中修复。 但是,将来可能会出现新的弱势群体。 捆绑软件的实现和添加新帐户的机制仍与以前相同。 它们仍然允许我们在检测旧的或新的易受攻击的Parcelable类时创建此精确利用。 而且,这些类仍然是手动实现的,程序员必须确保序列化的Parcelable对象的长度保持不变,这是所有隐含因素的人为因素。 但是,我们希望此类错误尽可能少,并且EvilParcel漏洞不会对Android用户构成威胁。

You can check your mobile device for EvilParcel vulnerabilities using our Dr.Web Security Space for Android. The built-in Security Auditor will report the detected issues and recommend ways to eliminate them.

您可以使用我们的Android Dr.Web安全空间来检查您的移动设备是否存在EvilParcel漏洞。 内置的安全审核员将报告检测到的问题并建议消除问题的方法。

翻译自: https://habr.com/en/company/drweb/blog/457610/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值