Talking about Logic Vulnerabilities of Android Components

foreword

As the society pays more and more attention to security, various defensive programming or vulnerability mitigation measures are gradually added to the operating system, such as code signing, pointer signing, address randomization, isolated heap, etc. Many common memory corruption vulnerabilities are often difficult to stably exploit under these mitigation measures. Therefore, attackers are gradually focusing more on logical loopholes. Logical loopholes usually have good stability and are not affected by Feng Shui; but at the same time, they are hidden deeply and are difficult to find in a large number of business codes. Moreover, due to the variety of forms, it is not very versatile, and may not be a high-priority research direction from the perspective of input-output ratio. Regardless, this is always an attack surface to watch. Therefore, this article introduces some common logic vulnerabilities targeting the Android platform.

four major components

Anyone who has been in contact with Android should have heard of the "four major components". The first thing to learn when developing an application is the life cycle of each component. The so-called four major components refer to Activity , Service , Broadcast Receiver  and  Content Provider respectively  . For the implementation details of these components, please refer to the official document: Application Fundamentals[1].

In security research, the four major components deserve our special attention, because they are an important bridge for the application to communicate with the outside world, and even within the application, these components are used to build a loosely coupled relationship with each other. For example, the application itself does not need to apply for camera permission, but the (system) camera application can open the camera and obtain the captured photos through the mutual communication between components, as if it was taking pictures by itself.

In the process of component interaction, the core data structure is Intent[2], which is the carrier of communication between most components.

Intent 101

According to the official statement, an Intent is an "abstract description of an operation to be performed". It can also be called an "intent" in literal translation. For example, if you want to turn on the camera to take pictures, open the browser to visit the URL, and open the settings interface , all can be described by Intent.

There are two main forms of Intent, namely explicit Intent and implicit Intent; the difference between the two is that the former is explicitly specified  Component, while the latter does not specify Component, but it will use enough information to help the system understand the intent, such as  ACTION, CATAGORY etc.

The main function of Intent is to start Activity, so we take this scenario as an example to analyze the specific implementation of Intent from the source code. The general code snippet for starting an Activity is as follows:

Intent intent = new Intent(context, SomeActivity.class);
startActivity(intent);

An explicit Intent is used here, but that's not the point. It is generally called in an Activity, so the code is called  Activity.startActivityin frameworks/base/core/java/android/app/Activity.java, and there is no copying and pasting here. In short, the calling link is as follows:

  • • Activity.startActivity()

  • • Activity.startActivityForResult()

  • • Instrumentation.execStartActivity()

  • • ActivityTaskManager.getService().startActivity()

  • • IActivityTaskManager.startActivity()

The last call is an interface, which is a very common pattern. The next step is to find its implementation. If there is no accident, this implementation should be in another process. In fact it is also in  system_server :

  • • ActivityTaskManagerService.startActivity()

  • • ActivityTaskManagerService.startActivityAsUser()

  • • ActivityStarter.execute()

The last method prepares to start the Activity through the information passed in earlier, including caller, userId, flags, callingPackage and the most important intent information, as follows:

private int startActivityAsUser(...) {
    // ...
    return getActivityStartController()
            .obtainStarter(
                intent, "startActivityAsUser")
            .setCaller(caller)
            .setCallingPackage(callingPackage)
            .setCallingFeatureId(callingFeatureId)
            .setResolvedType(resolvedType)
            .setResultTo(resultTo)
            .setResultWho(resultWho)
            .setRequestCode(requestCode)
            .setStartFlags(startFlags)
            .setProfilerInfo(profilerInfo)
            .setActivityOptions(bOptions)
            .setUserId(userId)
            .execute();
}

 The main logic of ActivityStarter.execute() is as follows:

int execute() {
    // ...
    if (mRequest.activityInfo == null) {
        mRequest.resolveActivity(mSupervisor);
    }
    res = resolveToHeavyWeightSwitcherIfNeeded();
    res = executeRequest(mRequest);

}

Among them, resolveActivity it is used to obtain the information of the Activity to be started. For example, in the case of implicit start, there may be multiple targets that meet the requirements, and a pop-up menu will ask the user which application to choose to open.  In the middle, it mainly checks relevant permissions, and calls to execute the real call executeRequest after all permissions meet the conditions  .startActivityUnchecked

I have introduced most of the processes in the Android12 application startup process analysis [3], and here we focus more on the role of Intent itself. From the above analysis, it can be seen as a message carrier in multi-process communication, and its source code definition can also be seen that Intent itself is a structure that can be serialized and passed between processes.

public class Intent implements Parcelable, Cloneable { ... }

Intent It has many methods and attributes, which will not be expanded here for the time being, and will be analyzed later when specific vulnerabilities are introduced. The following article mainly starts with the four major components, and introduces some common vulnerability modes and design traps respectively.

Activity

Activity[4], also known as the active window, is a graphical interface that directly interacts with the user. One of the main development tasks of APP is to design each activity and plan the jumps and links between them. Usually an activity represents a full-screen active window, but it can also exist in other forms, such as floating windows, multi-windows, etc. As a UI window, it generally uses XML files for layout, and inherits the Activity class to implement its life cycle functions  onCreate and  onPause other life cycle methods.

If the Activity defined by the developer wants to be  Context.startActivity started, it must be declared in the manifest file of the APP, namely AndroidManifest.xml[5]. When the application is installed, PackageManager it will parse the relevant information in its manifest file and register it in the system, so that it  resolve can be searched at any time.

In the adb shell, you can  am start-activity open the specified Activity and start it by specifying the Intent:

am start-activity [-D] [-N] [-W] [-P <FILE>] [--start-profiler <FILE>]
        [--sampling INTERVAL] [--streaming] [-R COUNT] [-S]
        [--track-allocation] [--user <USER_ID> | current] <INTENT>

As the carrier of the user interface, Activity carries many tasks such as user input/processing, external data reception/display, etc., so it is a main attack surface of the application. Several common attack scenarios are introduced below.

life cycle

The classic life cycle diagram of Activity is as follows:

Activity Lifecycle

Usually developers only need to implement  onCreate the method, but for some complex business scenarios, it is necessary to correctly understand its life cycle. Take an application that the author encountered in the internal test as an example. Some sensitive operations were performed in a certain Activity, such as turning on the camera streaming, or turning on the recording, but only turned off the streaming/recording in the app  onDestroy . This will cause these operations to still run in the background when the APP enters the background, and the attacker can construct a task stack so that the victim still executes the background functions of the target application when facing the phishing interface of the malicious application, thus forming a special phishing scenario. The correct approach should be  onPaused to close sensitive operations in the callback.

In fact, the attacker can precisely control the triggering timing of the target Activity lifecycle callback function by continuously sending different Intents. If no attention is paid during development, the state machine of the application function will be abnormal or even a security problem.

Implicit Exported

As mentioned earlier, if the Activity defined by the developer is to be used  startActivity to start, it must be declared in AndroidManifest.xml  <activity> . An example of a declaration is as follows:

<activity xmlns:android="http://schemas.android.com/apk/res/android" android:theme="@android:01030055" android:name="com.evilpan.RouterActivity">
  <intent-filter>
    <action android:name="android.intent.action.VIEW"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <category android:name="android.intent.category.BROWSABLE"/>
    <data android:scheme="demo" android:host="router"/>
  </intent-filter>
</activity>

Many properties are supported in activity[6]. One of the important attributes is  android:exportedto indicate whether the current Activity can be started by other application components. This attribute has several characteristics:

  1. 1. The attribute can be defaulted, and the default value defaults to  false;

  2. 2. If the Activity does not explicitly set this property and it is defined in the Activity  <intent-filter>, then the default value is  true;

That is to say, the developer may not explicitly specify the Activity export, but because it is specified  intent-filter, it is actually exported, that is, the corresponding Activity can be invoked by other applications. This situation was very common in the early days. For example, an APP designed a set of interfaces for changing passwords. You need to enter the old password first and then jump to the interface for entering the new password. If the latter is exported, the attacker can directly invoke the interface for entering the new password, thereby bypassing the verification logic of the old password.

Google has been deeply aware of this problem, so it stipulates that after Android 12, if the application's Activity contains intent-filter, it must be explicitly specified  android:exported as true or false, and the default is not allowed. In Android 12, an application that does not explicitly specify the exported attribute and has an intent-filter Activity will be directly rejected by the PackageManager during installation.

Fragment Injection

Activity, as the core component of the UI, also supports modular development, such as displaying several reusable sub-interfaces in the same interface. The Fragments[7] component, or "fragment", was born with this design idea. Use  FragmentActivity can combine one or more fragments in an Activity to facilitate code reuse, and the life cycle of fragments is affected by the host Activity.

The Fragment Injection vulnerability first broke out in 2013. Here we only introduce its principle. The original article and paper are attached at the end of this section. The core of the vulnerability is the class provided by the system  PreferenceActivity . Developers can inherit it to implement convenient setting functions. The onCreate function of this class has the following functions:

protected void onCreate() {
    // ...
    String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
    Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
    // ...
    if (initialFragment != null) {
        switchToHeader(initialFragment, initialArguments);
    }
}

private void switchToHeaderInner(String fragmentName, Bundle args) {
    getFragmentManager().popBackStack(BACK_STACK_PREFS,
            FragmentManager.POP_BACK_STACK_INCLUSIVE);
    if (!isValidFragment(fragmentName)) {
        throw new IllegalArgumentException("Invalid fragment for this activity: "
                + fragmentName);
    }

    Fragment f = Fragment.instantiate(this, fragmentName, args);
}

It can be seen that a string and a Bundle parameter are obtained from the Intent, and finally passed in  switchToHeaderInner for instantiation  Fragment. The instantiation process is as follows:

public static Fragment instantiate(Context context, String fname, Bundle args) {
    // ...
    Class clazz = sClassMap.get(fname);
    if (clazz == null) {
            // Class not found in the cache, see if it's real, and try to add it
            clazz = context.getClassLoader().loadClass(fname);
            sClassMap.put(fname, clazz);
    }
    Fragment f = (Fragment)clazz.newInstance();
    if (args != null) {
            args.setClassLoader(f.getClass().getClassLoader());
            f.mArguments = args;
    }
    return f;
}

A classic reflection call, instantiates the incoming string as a Java class and sets its parameters. What is this, this is deserialization! And the actual vulnerability comes from here. Since the incoming parameter is controllable by the attacker, the attacker can set it as an internal class, thus touching the functions that the developer did not expect. In the original report, the author used a Fragment that sets the PIN password in the Settings application as the target input. This is a private fragment, which leads to the function of modifying the PIN code without authorization. Many other user applications at that time also used PreferenceActivity, so the vulnerability had a wide impact, and the resulting exploits varied according to the functions of the application itself (that is, whether there are useful Gadgets).

Note that the above code is excerpted from the latest Android 13, in which  switchToHeaderInner the method has added  isValidFragment a judgment, which is one of Android's original fixes, that is, forcing subclasses of PreferenceActivity to implement this method, otherwise an exception will be thrown at runtime. But even so, there are still many developers who directly inherit and return for the sake of convenience  true .

Fragment Injection seems to be a problem of PreferenceActivity, but its core is still the imperfect verification of untrusted input. In the following examples, we will see similar vulnerability patterns many times.

Reference article:

  • • A New Vulnerability in the Android Framework: Fragment Injection[8]

  • • ANDROID COLLAPSES INTO FRAGMENTS.pdf (wp)[9]

  • • Understanding fragment injection[10]

  • • How to fix Fragment Injection vulnerability[11]

clickjacking

Since Activity is the main carrier of UI, the interaction with users is also a key function. In traditional web security, there has already been a method of clickjacking, which is to place the case that the target website wants the victim to click on in a designated location (such as an iframe), and use related components in the host to cover and guide the target, so that the victim performs sensitive operations without knowing it, such as liking coins, collecting one-click leave, etc.

Similar attack methods have also appeared in Android, such as covering the attacker's custom TextView in front of the system's sensitive pop-up window to guide the victim to confirm certain harmful operations. Of course, this requires the attacker's application to have the floating window permission ( SYSTEM_ALERT_WINDOW). In the newer Android system, the application for this permission requires multiple confirmations from the user.

In the past two years, some clickjacking vulnerabilities have also appeared in AOSP, including but not limited to:

  • • CVE-2020-0306: Bluetooth discovery request confirmation box override

  • • CVE-2020-0394: Bluetooth pairing dialog override

  • • CVE-2020-0015: Certificate installation dialog override

  • • CVE-2021-0314: Uninstall confirmation dialog override

  • • CVE-2021-0487: Calendar debug dialog override

  • • ...

For system applications, the way to defend against clickjacking is generally to  SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS prevent the UI from being covered by using the android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS permission and specifying it in the layout parameters.

For ordinary applications, it is impossible to apply for the HIDE_NON_SYSTEM_OVERLAY_WINDOWS permission. There are generally two defensive measures. One is to prohibit input events after the form is covered by setting the layout to  filterTouchesWhenObscured ;  true the other is to overload the View.onFilterTouchEventForSecurity method and detect the coverage of other applications in it. In Android 12, the system has enabled the filterTouchesWhenObscured attribute by default, which is also a classic implementation of security by default.

For the operation details and mitigation solutions of clickjacking, you can refer to this article from OPPO Security Lab:  "A Threat That Cannot Be Ignored: Clickjacking Attacks in Android"

Another vulnerability similar to clickjacking is called StrandHogg. For details, please refer to the original article below. The key point is to use  allowTaskReparenting the and  taskAffinity properties of Activity to disguise its task stack as the target application, so that when the target application is opened, due to the last-in-first-out feature of TaskStack, the user will see the attacker's application, resulting in a phishing scenario for the application.

Later, the same security team proposed  the StrandHogg 2.0  version, which mainly uses  ActivityStarter the AUTOMERGE feature in . Assuming there are two applications A and B, after being called in A1  startActivites(B1, A2, B2) , the task stack will be merged from (A1, B1) and (A2, B2) to (A1, B1, A2, B2), that is, the activities of other applications will be covered in the same task stack, resulting in a phishing scenario. However, this vulnerability is relatively specialized, so Google has already fixed it a long time ago. For details, please read the following reference articles:

  • • The StrandHogg vulnerability[12]

  • • StrandHogg 2.0 – New serious Android vulnerability[13]

  • • StrandHogg 2.0 (CVE-2020-0096) fix[14]

Intent Redirection

Intent Redirection, as the name implies, forwards the untrusted input passed in by the user, which is similar to the SSRF vulnerability on the server side. An example of a typical vulnerability is as follows:

protected void onCreate (Bundle savedInstanceState) {
   Intent target = (Intent) getIntent().getParcelableExtra("target");
   startActivity(target);
}

The target passed in by the user  Parcelable is directly converted into  Intent an object, and this object  startActivity is used as a parameter to call. As far as this example is concerned, the possible harm is that the attacker can use arbitrary structured Intent data to start any application in the target APP, even if it is a private application that has not been exported. However, the application whose target is not exported may further parse the parameters in the Intent provided by the attacker to cause further damage, such as executing arbitrary Javascript code in the built-in Webview, or downloading and saving files, etc.

In fact, Intent Redirection can be used for other interfaces besides possibly starting private Activity components, including:

  • • startActivity[15]

  • • startService[16]

  • • sendBroadcast[17]

  • • setResult[18]

Note: Each method may have several derivative methods, such as startActivityForResult

The first three may be easier to understand, namely start the interface, start the service and send the broadcast. The last one  setResult may be ignored during troubleshooting. This is mainly used to return additional data to the caller of the current Activity. It is mainly used in  startActivityForResult scenarios. It may also pollute the user's untrusted data to the caller.

From a defensive point of view, it is recommended not to directly send the incoming Intent as a parameter to the above four interfaces. If you must do this, you need to perform sufficient filtering and security verification in advance, such as:

  1. 1. Set the component itself  android:exported to  false, but this only prevents the data sent by the user actively, and cannot intercept the  setResult returned data;

  2. 2. Make sure that the obtained data comes  Intent from a trusted application, such as calling in the component context  getCallingActivity().getPackageName().equals("trust.app"), but pay attention to malicious applications that can  getCallingActivity return  by constructing data null;

  3. 3. Make sure that there is no harmful behavior to be forwarded  Intent , such as the component does not point to its own non-exported components, does not contain  FLAG_GRANT_READ_URI_PERMISSION , etc. (see the ContentProvider vulnerability below for details);

  4. 4. ...

But it turns out that even Google itself may not be able to ensure perfect verification. The high-risk vulnerability recently submitted by Wuheng Lab  CVE-2022-20223 is a typical example:

private void assertSafeToStartCustomActivity(Intent intent) {
    // Activity can be started if it belongs to the same app
    if (intent.getPackage() != null && intent.getPackage().equals(packageName)) {
        return;
    }
    // Activity can be started if intent resolves to multiple activities
    List<ResolveInfo> resolveInfos = AppRestrictionsFragment.this.mPackageManager
            .queryIntentActivities(intent, 0 /* no flags */);
    if (resolveInfos.size() != 1) {
        return;
    }
    // Prevent potential privilege escalation
    ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
    if (!packageName.equals(activityInfo.packageName)) {
        throw new SecurityException("Application " + packageName
                + " is not allowed to start activity " + intent);
    }
}

It is used  ActivityInfo.packageName to judge whether the package name of the startup target is consistent with the package name of the current caller, but in fact, the explicit Intent is to specify the startup target through componentName, which has higher priority and the  Intent.packageName latter can be forged, which causes the check to be bypassed. There is actually another vulnerability in the above few lines of code. If you are interested, you can refer to the reference link below.

So, when you come across a potential Intent redirection issue, take a little more time to scrutinize it, and you might be able to find an exploitable scenario.

Service

Service[20] has two main functions, one is to provide a background long-running environment for APP, and the other is to provide its own services externally. Similar to the definition of Activity, Service must be declared in the manifest before it can be used. Note that the code in the Service also runs on the main thread like the Activity, and is in the process of the application by default.

According to the distinction between the two main functions of the Service, there are two corresponding forms to start the Service:

  1. 1.  Context.startService(): Start the background service and let the system schedule it;

  2. 2.  Context.bindService(): Let the (external) application bind the service and use the interface it provides, which can be understood as the RPC server;

The life cycle diagram of the two ways to start the service is as follows:

Service Lifecycle

The blue part is called on the client side. After the system receives the request, it will start the corresponding service. If the corresponding process is not started, it will also notify zygote to start it. Regardless of the method used to create the service, the system calls  onCreate and  onDestroy methods for it. The overall process is similar to the startup process of Activity, so I won't go into details here.

The shell also provides  start-activity commands to facilitate starting services:

am start-service [--user <USER_ID> | current] <INTENT>

Let's introduce some vulnerabilities related to the Service component.

life cycle

The life cycle of Service startup was introduced earlier, which is generally similar to the Activity process, but there are a few differences that need to be noted:

  1. 1. Unlike the Activity lifecycle callback method, it is not necessary to call the superclass implementation of the Serivce callback method, such as onCreate, onDestory, etc.;

  2. 2.  Service The direct subclass of the class runs in the main thread, and generally needs to be executed in a new thread when processing multiple blocked requests at the same time;

  3. 3.  IntentService It is a subclass of Service, which is designed to run in the Worker thread and can process multiple blocked Intent requests serially; API-30 will be marked as an obsolete interface after API-30, and it is recommended to use WorkManager or JobIntentService to implement;

  4. 4. The client uses  stopSelf or  stopService to stop the binding service, but the server does not have a corresponding  onStop callback, and only receives it before it is destroyed  onDestory;

  5. 5. The foreground service must provide notifications for the status bar, allowing users to realize that the service is running;

For bound services [21], the Android system will automatically destroy the service according to the bound client reference count, but if the service implements the callback  onStartCommand() , the service must be explicitly stopped, because the system will regard it as a started state. In addition, if the service allows the client to bind again, it needs to implement the onUnbind method and return true, so that the client will receive the same IBinder when it binds next time, as shown in the example diagram below:

Rebind

The declaration cycle of a service is more complicated than that of an Activity, because it involves the binding relationship between processes, so it is more likely to write unrobust or even problematic code without understanding it.

Implicit Export

Like Activity, Service should also be declared in the manifest using service[22], and it also has  android:exported attributes. Even the definition of the default value of this attribute is the same, that is, the default is  false, but when the intent-filter is included, the default is  true. Similarly, in Android 12 and later, it is also mandatory to explicitly specify the export properties of the service.

service hijacking

Unlike Activity, Android does not recommend using implicit Intents to start services. Because the service runs in the background, there is no visible graphical interface, so the user cannot see which service was started by the implicit Intent, and the sender does not know who the Intent will be received by.

Service hijacking is a typical vulnerability. An attacker can declare the same intent-filter as the target for his Service and set a higher priority. In this way, the Intent that should be sent to the target service can be intercepted. If it contains sensitive information, it will also cause data leakage.

However,  bindService the harm in this situation is even more serious. The attacker can pretend to be the target IPC service to return wrong or even harmful data. Therefore, starting from Android 5.0 (API-21), using an implicit Intent to call bindService will directly throw an exception.

If the target application to be audited is provided in the Service  intent-filter, it is necessary to focus on troubleshooting.

AIDL

The binding service can be used as an IPC server. If the server returns an instance of the AIDL interface when binding, it means that the client can call any method of the interface. A practical case is Tiktok  IndependentProcessDownloadServiceDownloadService which returns an instance of the above AIDL interface in its onBind:

com/ss/android/socialbase/downloader/downloader/DownloadService.java:

if (this.downloadServiceHandler != null) {
    return this.downloadServiceHandler.onBind(intent);
}

And there is a  tryDownload method to specify the url and file path to download and save the file locally. Although the attacker does not have an AIDL file, he can still construct a legal request through reflection to call. The key code in the PoC is as follows:

private ServiceConnection mServiceConnection = new ServiceConnection() {
    public void onServiceConnected(ComponentName cName, IBinder service) {
        processBinder(service);
    }

    public void onServiceDisconnected(ComponentName cName) { }
};

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Intent intent = new Intent("com.ss.android.socialbase.downloader.remote");
    intent.setClassName(
        "com.zhiliaoapp.musically",
        "com.ss.android.socialbase.downloader.downloader.IndependentProcessDownloadService");
    bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
}

private void processBinder(IBinder binder) {
    ClassLoader cl = getForeignClassLoader(this, "com.zhiliaoapp.musically");
    Object handler = cl.loadClass("com.ss.android.socialbase.downloader.downloader.i$a")
            .getMethod("asInterface", IBinder.class)
            .invoke(null, binder);

    Object payload = getBinder(cl);

    cl.loadClass("com.ss.android.socialbase.downloader.downloader.i")
            .getMethod("tryDownload", cl.loadClass("com.ss.android.socialbase.downloader.model.a"))
            .invoke(handler, payload);
}

private Object getBinder(ClassLoader cl) throws Throwable {
    Class utilsClass = cl.loadClass("com.ss.android.socialbase.downloader.utils.g");
    Class taskClass = cl.loadClass("com.ss.android.socialbase.downloader.model.DownloadTask");
    return utilsClass.getDeclaredMethod("convertDownloadTaskToAidl", taskClass)
            .invoke(null, getDownloadTask(taskClass, cl));
}

The key is to use  Context.getForeignClassLoader to get the ClassLoader of other applications.

Vulnerability details reference: vulnerabilities in the TikTok Android app[23]

Intent Redirect

This is actually similar to the corresponding vulnerability in the Activity. When the client starts/binds the Service, it also specifies an implicit or explicit Intent. If the untrusted data is used by the server as a parameter to start other components, it may cause the same Intent redirection problem. Note that  getIntent() there are other sources of data, such as  onHandleIntent parameters implemented in the service.

In fact, the "LaunchAnywhere" vulnerability that first proposed the harm of Intent redirection came from the system service, to be precise, it is  AccountManagerService a vulnerability. The normal execution flow of AccountManager is:

  1. 1. A common application (denoted as A) requests to add a certain type of account, calling AccountManager.addAccount;

  2. 2. AccountManager will search for the Authenticator class of the application (denoted as B) that provides the account;

  3. 3. AccountManager calls the Authenticator.addAccount method of B;

  4. 4. AccountManager invokes B's account login interface (AccountManagerResponse.getParcelable) according to the Intent returned by B;

In step 4, the system thinks that the data returned by B points to B's login interface, but in fact B can make it point to other components, even system components, which creates an Intent redirection vulnerability. The source of Intent here is rather tortuous, but the essence is still controllable by the attacker.

For details about this vulnerability and the exploit process, please refer to: launchAnyWhere: Analysis of Activity Component Permission Bypass Vulnerability (Google Bug 7699048)[24]

Receiver

Broadcast Receiver[25], receiver for short, is the broadcast receiver. The linkage between Activity and Service described above is one-to-one, and in many cases we may want one-to-many or many-to-many communication solutions, and broadcasting assumes this function. For example, the Android system itself will send broadcast notifications to all interested applications when various events occur, such as turning on airplane mode, network status changes, low battery, and so on. This is a typical publish/subscribe design pattern, as is the carrier of broadcast data  Intent.

Different from the previous Activity and Service, Receiver can be declared and registered in the manifest, which is called static registration; it can also be registered dynamically during the running of the application. But in any case, the defined broadcast receiver must inherit from BroadcastReceiver[26] and implement its life cycle method  onReceive(context, intent).

Note that the parent class of BroadcastReceiver is Object, unlike Activity and Service which are Context, so onReceive will also pass in an additional context object.

The command to send a broadcast in the shell is as follows:

am broadcast [--user <USER_ID> | all | current] <INTENT>

Here are some frequently asked questions in order.

Implicit Export

There is nothing special about using statically registered receivers, examples are as follows:

<receiver android:name=".MyBroadcastReceiver"  android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
        <action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
    </intent-filter>
</receiver>

There is also the same default export problem as before. I believe everyone is tired of seeing it, so I won’t be verbose anymore. Then look at the dynamic registration situation, such as:

BroadcastReceiver br = new MyBroadcastReceiver();
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
this.registerReceiver(br, filter);

It may be easier to ignore the issue of export permissions with dynamic registration than with definitions in the manifest. The above code snippet dynamically registers a broadcast, but does not explicitly declare the exported attribute, so it is exported by default. In fact, there seems to be no easy way to set up using registerReceiver  exported=false, and Google's official suggestion is to register the use of broadcast receivers that do not need to be exported  LocalBroadcastManager.registerReceiver , or specify permission permissions when registering.

For the case of specifying the permission permission, if it is a custom permission, it needs to be declared in the application list, for example:

<permission android:name="com.evilpan.MY_PERMISSION"
    android:protectionLevel="signature"/>
<uses-permission android:name="com.evilpan.MY_PERMISSION" />

signature Indicates a permission that the system will only grant if the app requesting authorization is signed with the same certificate as the app declaring the permission. If the certificates match, permissions are automatically granted without notifying the user or asking for their explicit permission. See protectionLevel[27] for details.

Finally, specify this permission during dynamic registration:

this.registerReceiver(br, filter, "com.evilpan.MY_PERMISSION", null);

Registering an export broadcast receiver without permission restrictions will result in receiving malicious data forged by the attacker. If the verification is not done properly during onReceive, there may be vulnerabilities such as unauthorized access or Intent redirection, causing further security hazards.

There are many security problems of this kind, and the typical one is the PpmtReceiver vulnerability used to break through the Samsung Galaxy S8 on Pwn2Own[28].

information leakage

The above is mainly to set permissions from the perspective of restricting broadcast senders, but in fact this permission can also restrict broadcast receivers, but additional designation is required when sending messages. For example, if you want to only allow receivers with the above permissions to receive broadcasts, the sending code is as follows:

Intent it = new Intent(this, ...);
it.putExtra("secret", "chicken2beautiful")
sendBroadcast(it, "com.evilpan.MY_PERMISSION");

If there is no second parameter, the default is that all recipients that meet the conditions can receive the broadcast information. At this time, if the sent Intent contains sensitive data, it may cause information leakage.

A practical case is CVE-2018-9581[29]. The system carries sensitive data RSSI when broadcasting android.net.wifi.RSSI_CHANGED. This broadcast can be received by all applications, which indirectly leads to the leakage of physical location information. (funny?)

It can be seen that for Broadcast Receiver, the role of the permission tag is particularly obvious. For system broadcasts, for example  BOOT_COMPLETED, usually only system apps have permission to send them. This is defined in the AndroidManifest.xml[30] of the framework.

As for the custom broadcast of the application, the above-mentioned custom permissions are usually used, so a question naturally comes to mind, what happens if multiple applications define the same permission? In fact, this is a historical vulnerability. In the early days of Android, the strategy was to give priority to the first defined permission, but after Andorid 5, it has been clearly defined that two applications have different definitions of the same permission (unless they have the same signature), otherwise the application installed later will have an error warning  INSTALL_FAILED_DUPLICATE_PERMISSION . Interested archaeologists can refer to the following related articles:

  • • Vulnerabilities with Custom Permissions[31]

  • • Custom Permission Vulnerability and the 'L' Developer Preview[32]

Intent Redirection

Not much to say about the principle, just look at the case. The vulnerability lies in Tiktok  NotificationBroadcastReceiver . The definition of intent-filter causes the component to be set as export by default, so it can receive the broadcast of external applications, and directly use the untrusted data in the broadcast to start the Activity, as follows:

NotificationBroadcastReceiver

Vulnerability details can refer to: Oversecured detects dangerous vulnerabilities in the TikTok Android app[33]

ContentProvider

Content Provider[34], that is, the content provider, is referred to as Provider for short. Android applications are usually implemented as an MVC structure (Model-View-Controller), and the Model part is the data source for its own View, or graphical interface, to display. But sometimes an application will want to provide its own data for other data to use, or obtain data from other applications.

To define a ContentProvider, you only need to inherit from the ContentProvider[35] class and implement six methods:  query, insertupdatedeletegetType and  onCreate. Among them, except onCreate is called by the system in the main thread, other methods are actively called by the client program. A custom provider must be declared in the program list, which will be described in detail later.

It can be seen that the Provider mainly implements the database-like addition, deletion, modification and query interface. From the perspective of the client, the query process is similar to querying the traditional database. For example, the following is the code snippet for querying the system SMS:

Cursor cursor = getContentResolver().query(
    Telephony.Sms.Inbox.CONTENT_URI,           // 指定要查询的表名
    new String[] { Telephony.Sms.Inbox.BODY }, // projection 指定索要查询的列名
    selectionClause,                           // 查询的过滤条件
    selectionArgs,                             // 查询过滤的参数 
    Telephony.Sms.Inbox.DEFAULT_SORT_ORDER);   // 返回结果的排序
while (cursor.moveToNext()) {
    Log.i(TAG, "msg: " + cursor.getString(0));
}

Among them  ContentResolver is  ContentInterface the subclass, which is the client remote interface of ContentProvider, which can realize its transparent remote proxy invocation. content_uri Can be regarded as the table name of the query, projection can be regarded as the column name, and the returned cursor is the iterator of the query result row.

Unlike the previous three components, the tool for accessing the provider component in the shell is  content.

Let's introduce the common problems in Provider.

Permissions

In view of the provider as a data carrier, security access and permission control are naturally the top priority. For example, in the above code example, the interface for accessing SMS, if everyone can access it at will, it will obviously cause information leakage. As briefly mentioned earlier, the Provider defined in the application must be declared in its program manifest file, using the provider[36] tag. Among them are our common  exported attributes, indicating whether it can be accessed externally, permission and the attributes indicate the permissions required for access. Of course, different permissions can be used for reading and writing, such as  readPermission/ writePermission attributes.

For example, the SMS database mentioned above is declared as follows:

<provider android:name="SmsProvider"
    android:authorities="sms"
    android:multiprocess="false"
    android:exported="true"
    android:singleUser="true"
    android:readPermission="android.permission.READ_SMS" />

If other applications want to access, they need to declare the corresponding permissions in the manifest file.

<uses-permission android:name="android.permission.READ_SMS" />

This is all well understood, and other components have similar characteristics. In addition, Provider itself provides more fine-grained permission control, namely grantUriPermissions[37]. This is a boolean indicating whether to temporarily grant clients access to this provider. The operation process of temporarily granting permissions is generally as follows:

  1. 1. The client sends an Intent to the application where the Provider is located, specifying the Content URI to be accessed, such as using  startActivityForResult Send;

  2. 2. After the application receives the Intent, it judges whether it is authorized. If it is confirmed, it prepares an Intent and sets the flags flag  FLAG_GRANT_[READ|WRITE]_URL_PERMISSIONto indicate that it is allowed to read/write the corresponding Content URI (it may not be consistent with the requested URI), and finally uses it and returns it  setResult(code, intent) to the client;

  3. 3. The onActivityResult of the client receives the returned Intent, and uses the URI in it to temporarily access the target Provider;

Taking reading as an example, Intent.flags if FLAG_GRANT_READ_URI_PERMISSION[38] is included, the receiver of the Intent (ie, the client) will be granted  Intent.data temporary read permission for part of the URI until the life cycle of the receiver ends. In addition, the Provider application can also actively call  Context.grantUriPermission methods to grant corresponding permissions to the target application:

public abstract void grantUriPermission (String toPackage, 
                Uri uri, 
                int modeFlags)
public abstract void revokeUriPermission (String toPackage, 
                Uri uri, 
                int modeFlags)

The grantUriPermissions attribute can perform read-write control on permissions at the URI granularity, but there is one point to note: the permissions temporarily granted through grantUriPermissions will ignore the restrictions imposed by the readPermission, writePermission, permission, and exported attributes . In other words, even if  exported=falsethe client does not apply for the corresponding one  uses-permission, once the permission is granted, it can still access the corresponding Content Provider!

In addition, <provider> there is a sub-tag grant-uri-permission[39]. Even if grantUriPermissions is set to  false, the URI subset defined under this tag can still be accessed by temporarily obtaining permissions. This subset can use prefixes or wildcards to specify the authorized path range of the URI.

Improper setting of Provider permissions may lead to application data being accessed by unexpected malicious programs, which may lead to information leakage, or cause the sandbox data to be overwritten and cause RCE. We will see many such cases later.

FileProvider

As mentioned earlier, a custom Provider needs to implement six methods, but Android has written corresponding subclasses for Providers in some common scenarios. Users can inherit these subclasses and implement a few subclass methods as needed. One of the common scenarios is to use ContentProvider to share application files. The system provides it  FileProvider to facilitate the sharing and access of application-defined files. However, if it is not used properly, it may cause problems of reading and writing arbitrary files.

FileProvider[40] provides the function of using XML to specify file access control. Generally, Provider applications only need to inherit the FileProvider class:

public class MyFileProvider extends FileProvider {
   public MyFileProvider() {
       super(R.xml.file_paths)
   }
}

file_paths is user-defined XML, and can also be  meta-data specified in the manifest file:

<provider xmlns:android="http://schemas.android.com/apk/res/android" android:name="com.evilpan.MyFileProvider" android:exported="false" android:authorities="com.evilpan.fileprovider" android:grantUriPermissions="true">
  <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@7F15000E"/>
</provider>

resource point to  res/xml/file_paths.xml. The file paths that can be accessed are defined in this file, and FileProvider will only generate Content URIs for files specified in advance. An example file path configuration is as follows:

<paths>
  <root-path name="root" path=""/>
  <files-path name="internal_files" path="."/>
  <cache-path name="cache" path=""/>
  <external-path name="external_files" path="images"/>
</paths>

paths Tags support multiple types of sub-tags, corresponding to sub-paths of different directories:

  • • files-path: Context.getFilesDir()

  • • cache-path: Context.getCacheDir()

  • • external-path: Environment.getExternalStorageDirectory()

  • •  external-files-path: Context.getExternalFilesDir()

  • • external-cache-path: Context.getExternalCacheDir()

  • • external-media-path: Context.getExternalMediaDirs()[0]

More specifically  root-path, it represents the root directory of  the system/ . The URI format generated by FileProvider is generally  content://authority/{name}/{path}, such as for the above Provider, which can be used  content://com.evilpan.fileprovider/root/proc/self/maps to access  /proc/self/maps files.

It can be seen that the FileProvider specification  root-path is a dangerous sign. Once the attacker obtains temporary permissions, he can read all the private data of the application.

For example, there has been such a real loophole in the history of TikTok:

<provider android:name="android.support.v4.content.FileProvider" android:exported="false" android:authorities="com.zhiliaoapp.musically.fileprovider" android:grantUriPermissions="true">
        <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/k86"/>
    </provider>

It is used directly here  FileProvider, without even needing inheritance. The content of the xml/k86.xml file is as follows:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:amazon="http://schemas.amazon.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
    <root-path name="name" path=""/>
    <external-path name="share_path0" path="share/"/>
    <external-path name="download_path2" path="Download/"/>
    <cache-path name="gif" path="gif/"/>
    ...
</paths>

After obtaining the temporary permission, the application can read and write arbitrary files.

The Hidden ...

In the ContentProvider class, in addition to the six methods that must be implemented, there are some other hidden methods, which are generally implemented by default or can be overridden by subclasses, such as

  • • openFile

  • • openFileHelper

  • • call

  • • ...

These hidden methods may inadvertently cause security problems. This section will analyze the reasons through some cases.

openFile

If ContentProvider wants to implement the function of reading and writing shared files, it can also  openFile be implemented by overriding the method. The default implementation of this method will throw  FileNotFoundException an exception.

Although the developer will not directly return the opened local file in implementation, but selectively return some subdirectory files. However, if the code is not written rigorously, problems such as path traversal may occur. A classic vulnerability implementation is as follows:

 @Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
    File file = new File(getContext().getFilesDir(), uri.getPath());
    if(file.exists()){
        return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
    }
    throw new FileNotFoundException(uri.getPath());
}

Another similar method of the same family  openAssetFile, whose default implementation calls openFile:

public @Nullable AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode)
        throws FileNotFoundException {
    ParcelFileDescriptor fd = openFile(uri, mode);
    return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
}

Sometimes developers know that they need to defend against path traversal, but the posture of defense is wrong, and there is also the possibility of being bypassed, for example:

public ParcelFileDescriptor openFile(Uri uri, String mode) {
    File f = new File(DIR, uri.getLastPathSegment());
    return ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
}

Here I want getLastPathSegment to use it to  get only the file name of the last level, but in fact it can be bypassed by the URL encoded path, for example, %2F..%2F..path%2Fto%2Fsecret.txt it will return  /../../path/to/secret.txt.

Yet another false defense is to use  UriMatcher.match method lookups  ../, which are also bypassed by URL encoding. The correct way to defend and filter is as follows:

public ParcelFileDescriptor openFile (Uri uri, String mode) throws FileNotFoundException {
  File f = new File(DIR, uri.getLastPathSegment());
  if (!f.getCanonicalPath().startsWith(DIR)) {
    throw new IllegalArgumentException();
  }
  return ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
}

For details, see: Path Traversal Vulnerability[41]

openFileHelper

There is also a little-known  openFileHelper method in ContentProvider. Its default implementation is to use the column data in the current Provider  _data to open the file. The source code is as follows:

protected final @NonNull ParcelFileDescriptor openFileHelper(@NonNull Uri uri,
        @NonNull String mode) throws FileNotFoundException {
    Cursor c = query(uri, new String[]{"_data"}, null, null, null);
    int count = (c != null) ? c.getCount() : 0;
    if (count != 1) {
        // If there is not exactly one result, throw an appropriate
        // exception.
        if (c != null) {
            c.close();
        }
        if (count == 0) {
            throw new FileNotFoundException("No entry for " + uri);
        }
        throw new FileNotFoundException("Multiple items at " + uri);
    }

    c.moveToFirst();
    int i = c.getColumnIndex("_data");
    String path = (i >= 0 ? c.getString(i) : null);
    c.close();
    if (path == null) {
        throw new FileNotFoundException("Column _data not found.");
    }

    int modeBits = ParcelFileDescriptor.parseMode(mode);
    return ParcelFileDescriptor.open(new File(path), modeBits);
}

The main function of this method is to facilitate subclasses to quickly implement  openFile methods, and usually do not override them directly in subclasses. However, due to the feature of opening files based on  _data columns, attackers may insert malicious data and indirectly achieve arbitrary file reading and writing.

A classic case is a Samsung phone  SemClipboardProviderthat does not verify user data when plugged in:

public Uri insert(Uri uri, ContentValues values) {
    long row = this.database.insert(TABLE_NAME, "", values);
    if (row > 0) {
        Uri newUri = ContentUris.withAppendedId(CONTENT_URI, row);
        getContext().getContentResolver().notifyChange(newUri, null);
        return newUri;
    }
    throw new SQLException("Fail to add a new record into " + uri);
}

The Provider is in  system_server the process and has a very high operating authority. By exploiting this vulnerability, an attacker can read and write arbitrary files at the system level. The PoC is as follows:

ContentValues vals = new ContentValues();
vals.put("_data", "/data/system/users/0/newFile.bin");
URI semclipboard_uri = URI.parse("content://com.sec.android.semclipboardprovider")
ContentResolver resolver = getContentResolver();
URI newFile_uri = resolver.insert(semclipboard_uri, vals);
return resolver.openFileDescriptor(newFile_uri, "w").getFd(); 

This vulnerability has been used in wild attacks together with other vulnerabilities and was captured by the Google TAG team. For the analysis of this Fullchain, please refer to Project Zero's recent article: A Very Powerful Clipboard: Analysis of a Samsung in-the-wild exploit chain[42]

call

ContentProvider provides  call methods for calling server-side defined methods, and its function signature is as follows:

public Bundle call (String authority, 
                String method, 
                String arg, 
                Bundle extras)
public Bundle call (String method, 
                String arg, 
                Bundle extras)

The default implementation is an empty function that returns directly  null. Developers can override this function to implement some dynamic methods, and the return value will also be passed back to the caller.

It looks similar to a regular RPC call, but there is a small trap here, which is also specially marked in the developer documentation: the Android system does not check the permissions of the function, because the system does not know whether the data is read or written  call in the call, so it cannot be judged according to the permission constraints defined in the Manifest. Therefore, developers are required to perform permission verification on the logic in the call.

If the developer implements this method, but does not perform verification or the verification is insufficient, unauthorized calls may occur. One case is that   in the implementation of the call method in a system application of OPPO, DexClassLoader was used to directly load the incoming dex file, which directly caused the attacker's code to run in a privileged process, and all Providers that inherited the base class would be affected () CVE-2021-23243HostContentProviderBase

SliceProvider In addition, in some system providers, some remote object instances can be obtained through the call method. For example, in the article Special attack surface in  Android (3) - hidden call function [43], the author obtained the PendingIntent object inside the system application, and further used it to realize the function of forging any broadcast  KeyguardSliceProvider .

other

In addition to the above-mentioned vulnerabilities directly related to the four major components, there are many vulnerabilities in the Android system that are not easy to classify. This section mainly selects a few of the most common vulnerabilities for a brief introduction.

PendingIntent

PendingIntent[44] is the representation of Intent, not an Intent object itself, but a Parcelable object. After the object is passed to other applications, other applications can perform the operation specified by the pointed Intent as the sender. A PendingIntent is created using one of the following static methods:

  • • getActivity(Context, int, Intent, int);

  • • getActivities(Context, int, Intent[], int);

  • • getBroadcast(Context, int, Intent, int);

  • • getService(Context, int, Intent, int);

PendingIntent itself is just a reference to the raw data descriptor by the system, which can be roughly understood as  Intent 的指针. Because of this, even after the application that created the PendingIntent is closed, other applications can still use the data. If the original app is later restarted and creates a PendingIntent with the same parameters, the PendingIntent actually returned will point to the same token as the one created earlier. Note that the filterEquals[45] method is used to judge whether the Intents are the same, which will judge whether the action, data, type, identity, class, and categories are the same. Note that they are not  extra listed here, so Intents with only different extras will also be considered equal.

Since PendingIntent can represent the characteristics of other applications, it may be used for abuse in some scenarios. For example, if a developer creates a default PendingIntent like this and passes it to other applications:

pi = PendingIntent.getActivity(this, 0, new Intent(), 0);
bundle.putParcelable("pi", pi)
// send bundle

After receiving this PendingIntent, a malicious application can obtain the original intent and use it  Intent.fillin to fill the empty field. If the original intent is the above empty intent, the attacker can modify it to a specific intent to start the application as the target, including private applications that have not been exported. A classic case is the early broadAnywhere[46] vulnerability. The addAccount method in the Android Settings application creates a PendingIntent broadcast, but the content of the intent is empty, which leads to the malicious application receiving the intent to fill in the action of the broadcast, so as to achieve unauthorized sending of system broadcasts, forged text messages, and restore factory settings.

In order to alleviate such problems, Andorid has made many restrictions on the rewriting of Intent.fillin, such as the existing fields cannot be modified, the component and selector fields cannot be modified (unless FILL_IN_COMPONENT/SELECTOR is additionally set), the action of the implicit Intent cannot be modified, etc.

However, some researchers have proposed a method for exploiting implicit Intents, that is, modifying the flag to add FLAG_GRANT_WRITE_URI_PERMISSION, modifying the data URI to point to the victim’s private Provider, and changing the package to the attacker; at the same time, the attacker declares the same intent filter in his own Activity, so that the attacker’s application will be launched when the intent is forwarded, and at the same time, access to the target’s private Provider will be obtained, so as to steal or overwrite private files. . For details about the attack idea, you can read the following reference article.

  • • broadAnywhere: Broadcast component permission bypass vulnerability (Bug: 17356824) [47]

  • • PendingIntent Redirection: A Universal Privilege Escalation Method for Android and Popular Apps - BlackHat EU 2021 Topic Details (Part 1)[48]

  • • PendingIntent Redirection: A Universal Privilege Elevation Method for Android Systems and Popular Apps - Detailed Explanation of BlackHat EU 2021 Issues (Part 2)[49]

After Android 12+, PendingIntent is required to be explicitly specified when it is created  FLAG_MUTABLE or  FLAG_IMMUTABLEwhether it can be modified.

Deep Link

In most operating systems, there is the concept of deeplink, that is, to open a specific application through a custom schema. For example, clicking https://evilpan.com/ can invoke the default browser to open the target page, clicking tel://10086 will invoke the dial interface, clicking weixin://qr/xxx will invoke WeChat, and so on. Regardless of other systems, in Android this is mainly achieved through implicit Intent.

If an application wants to register a similar custom protocol, it needs to declare it in the application manifest file:

<intent-filter>
  <action android:name="android.intent.action.VIEW"/>
  <category android:name="android.intent.category.DEFAULT"/>
  <category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="weixin" android:host="qr"/>
</intent-filter>

Because this type of implicit Intent can be triggered directly by clicking on a link, it is more popular with attackers. If the component that handles the corresponding Intent fails to filter the content passed in by the user, it is likely to cause a 1-click vulnerability. For related cases, please refer to the article: Special attack surface in Android (2) - dangerous deeplink

Webview

In the Andorid system, Webview[50] is mainly used to display webpage content in the application's own Activty, and provides some additional interfaces for developers to implement custom control. Higher scalability means more possibilities for errors, especially now that Android client development is declining, and Java development is also developing in the direction of "big front end". Many logics that were originally implemented using native applications have been gradually transferred to web pages, such as h5, applets, etc. In this way, the attack surface of webview has also expanded a lot.

Conventional Webview security issues are mainly related to some insecure configurations, such as overriding and  onReceivedSslError ignoring SSL errors leading to man-in-the-middle attacks and setAllowFileAccessFromFileURLs leakage of local private files. But now the vulnerabilities are more in JSBridge, which is a bridge between Java code and JavaScript code in web pages.

Due to the sandbox feature of the Webview or JS engine, the Javascript code in the webpage itself cannot perform many operations that can only be performed by native applications, such as sending broadcasts from Javascript, accessing application files, and so on. Due to the complexity of the business, many logics must be implemented in the Java layer or even the Native layer, so this requires the use of JSBridage. The traditional JSBridge is  Webview.addJavascriptInterface implemented, a simple example is as follows:

class JsObject {
    @JavascriptInterface
    public String toString() { return "injectedObject"; }
}
webview.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(new JsObject(), "injectedObject");
webView.loadData("", "text/html", null);
webView.loadUrl("javascript:alert(injectedObject.toString())");

Java layer returns data to Javascript by directly using loadUrl to execute JS code. Of course, in addition to registering Bridge in this way, there are many application-specific implementations, such as using  console.log data transmission and using  onConsoleMessage callbacks to receive it in the Java layer. But in any case, this leads to an increase in the attack surface. Large-scale applications even register hundreds of jsapis for web page calls.

From the perspective of historical vulnerabilities, the main cause of the Webview vulnerability is the jsapi domain name verification problem and the vulnerability of the Bridge code itself, which will not be expanded due to space reasons.

postscript

This article mainly introduces a series of related logical issues through the four major components of Android, and covers the historical vulnerabilities that the author knows as much as possible. Due to the limited level of personal cognition, it is always inevitable to miss a million things, but even so, the length of the article is still more than expected by a billion points. From the perspective of learning from the past, the best strategy to dig out such logic vulnerabilities is to use static analysis tools to collect more sink patterns and write effective rules for scanning. If there is no condition, (rip)grep is also possible.

References

  • • Galaxy Leapfrogging Pwning the Galaxy S8[51]

  • • Chainspotting: Building Exploit Chains with Logic Bugs[52] (How to break Samsung S8 with 11 exploits[53])

  • • Huawei Mate 9 Pro Mobile Pwn2Own 2017[54]

  • • Detect dangerous vulnerabilities in the TikTok Android app - Oversecured[55]

  • • Mystique Vulnerability White Paper - JD Research Institute Information Security Laboratory [56]

  • • HACKING XIAOMI'S ANDROID APPS - Part1[57]

  • • Automating Pwn2Own with Jandroid[58]

Guess you like

Origin blog.csdn.net/ab6326795/article/details/131206584