Post

FotaProvider apk Analysis

This blog is a writeup for the Exercise 3 from MaddieStone’s Android App Reverse Engineering 101

Intro

The exercise was to reverse engineer and find the vulnerability in Adups OTA application. The sample given was FotaProvider.apk.

This was the problem statement :

You are auditing a set of phones for security issues prior to allowing them onto your enterprise network. You are going through the apps that come pre-installed. For this pre-installed application, you are concerned that there may be a vulnerability that allows it to run arbitrary commands.

Static Analysis

When I started dissecting this apk, the first question I wanted to find an answer to was - how can we run arbitrary commands in android.

Did a little searching and got this :

In Android, running arbitrary shell commands programmatically requires appropriate permissions and careful handling due to security concerns.

Runtime.exec():

This method allows you to execute a shell command. For example:

1
2
3
4
5
try {
    Process process = Runtime.getRuntime().exec("your_command_here");
} catch (IOException e) {
    e.printStackTrace();
}

ProcessBuilder:

Provides more control over the environment and the ability to redirect input, output, and error streams. For example:

1
2
3
4
5
6
7
try {
    Process process = new ProcessBuilder()
            .command("your_command_here")
            .start();
} catch (IOException e) {
    e.printStackTrace();
}

Intent.ACTION_RUN:

We can invoke certain shell commands indirectly through intents. For example:

1
2
3
4
Intent intent = new Intent(Intent.ACTION_RUN);
intent.setClassName("com.android.settings", "com.android.settings.TestingSettings");
intent.putExtra("test", "test");
startActivity(intent);

I found that Runtime.getRuntime().exec(str) is being used, but the string which is being passed as the argument is hardcoded in the function :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static boolean i() {
        String str = System.getenv("PATH");
        ArrayList arrayList = new ArrayList();
        String[] split = str.split(":");
        for (int i = 0; i < split.length; i++) {
            arrayList.add("ls -l " + split[i] + "/su");
        }
        ArrayList a2 = a("/system/bin/sh", arrayList);
        String str2 = "";
        for (int i2 = 0; i2 < a2.size(); i2++) {
            str2 = String.valueOf(str2) + ((String) a2.get(i2));
        }
        return str2.contains("-rwsr-sr-x root     root");
    }

So, that’s a deadend. Next, I went ahead and searched for all these functions and got results for ProcessBuilder, it was being used in SysService and WriteCommandReceiver.

alt text

  • SysService

This activity is exported. Exported services are accessible to other applications, meaning that other apps can invoke or interact with them. When a service is exported, it allows components from other applications to start, bind to, or interact with that service, if they have the necessary permissions.

Processbuilder is used in the defined method

1
2
3
4
public static int a(String str) {
        String str2 = "cmd = " + str;
        return a(str.split(" "));
    }

When this method is called with the string as argument, another call to private method a happens. , contents of which are in java bytecode, I guess jadx was not able to decompile this. But anyways, it is easy to reverse this.

1
2
3
4
5
6
7
8
            r2 = 0
            r0 = -1
            java.lang.ProcessBuilder r3 = new java.lang.ProcessBuilder     // Catch: java.lang.Throwable -> L86 java.lang.Exception -> Lb0 java.io.IOException -> Lbe
            r3.<init>(r7)     // Catch: java.lang.Throwable -> L86 java.lang.Exception -> Lb0 java.io.IOException -> Lbe
            java.io.ByteArrayOutputStream r1 = new java.io.ByteArrayOutputStream     // Catch: java.lang.Throwable -> L86 java.lang.Exception -> Lb0 java.io.IOException -> Lbe
            r1.<init>()     // Catch: java.lang.Throwable -> L86 java.lang.Exception -> Lb0 java.io.IOException -> Lbe
            java.lang.Process r4 = r3.start()     // Catch: java.lang.Throwable -> La5 java.lang.Exception -> Lb5 java.io.IOException -> Lc3
            java.io.InputStream r3 = r4.getErrorStream()     // Catch: java.lang.Throwable -> La9 java.lang.Exception -> Lb9 java.io.IOException -> Lc7

A new object of the processbuilder is created and it is initated with and the string is being passed. Then r3.start() is called which executed the command passed via the string. But I couldn’t get a headway here because this method is not being called anywhere else.

  • WriteCommandReceiver

This is also an exported activity and after going through the code things get a little better.

The method is being called in the onRecieve method which is a broadcast receiver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    public void onReceive(Context context, Intent intent) {
        this.a = context;
        String action = intent.getAction();
        if (action.equalsIgnoreCase("android.intent.action.AdupsFota.WriteCommandReceiver")) {
            File file = new File(intent.getStringExtra("PackageFileName"));
            ((PowerManager) this.a.getSystemService("power")).newWakeLock(1, "FotaSys").acquire(5000L);
            try {
                RecoverySystem.installPackage(this.a, file);
            } catch (IOException e) {
                String str = "WriteCommandReceiver:Can't perform update system, IOException = " + e;
            }
        } else if (action.equalsIgnoreCase("android.intent.action.AdupsFota.operReceiver")) {
            String stringExtra = intent.getStringExtra("cmd");
            String str2 = "cmd : " + stringExtra;
            if (stringExtra != null) {
                a(stringExtra.split(" "));
            }
        }
    }

The string extra “cmd” is retrieved from the intent. Whenever this broadcast receiver receives something this method is automatically called. The action of the intent is retrieved. If the action is "android.intent.action.AdupsFota.operReceiver", the string extra “cmd” is retrieved from the intent. If the “cmd” is not null, it is split into an array and the a method is called which in turn executes arbitrary commands which are passed through as the cmd string.

Conclusion

In the manifest file we can observe that the user id is android:sharedUserId="android.uid.system" which is the most privileged user ids on an android device.

Also we could identify that the acticity WriteCommandReceiver is exported, that means any app on the device can interact and it is possible to execute arbitrary commands.

And with that we can conclude that this application is indeed vulnerable.

This post is licensed under CC BY 4.0 by the author.