‹ MobiSec

Mobile Hacking Labs - Cyclic Scanner

Aug 26, 2024

Challenge Overview

Due to a poorly configured scanning service, the Cyclic Scanner lab, explores a vulnerability that leads to RCE and may be used to escalate priveleges.

Recon & Discovery

A quick recon shows that the app has one activity (.MainActivity) that needs permission to access the external storage.

Running the application

With the permission, the app launches to an activity (main) that has a switch to enable the scanner.

Reverse Engineering & Code Analysis

<service
    android:name="com.mobilehackinglab.cyclicscanner.scanner.ScanService"
    android:exported="false"/>

From the manifest, the service is not exported, and that means we can only start it through the activity.

private final void startService() {
    Toast.makeText(this, "Scan service started", 0).show();
    startForegroundService(new Intent(this, (Class<?>) ScanService.class));
}

The MainActivity starts a service ScanService.class when the switch is set up (enabled).

public void handleMessage(Message msg) {
    Intrinsics.checkNotNullParameter(msg, "msg");
    try {
        System.out.println((Object) "starting file scan...");
        File externalStorageDirectory = Environment.getExternalStorageDirectory();
        Intrinsics.checkNotNullExpressionValue(externalStorageDirectory, "getExternalStorageDirectory(...)");
        Sequence $this$forEach$iv = FilesKt.walk$default(externalStorageDirectory, null, 1, null);
        for (Object element$iv : $this$forEach$iv) {
            File file = (File) element$iv;
            if (file.canRead() && file.isFile()) {
                System.out.print((Object) (file.getAbsolutePath() + "..."));
                boolean safe = ScanEngine.INSTANCE.scanFile(file);
                System.out.println((Object) (safe ? "SAFE" : "INFECTED"));
            }
        }
        System.out.println((Object) "finished file scan!");
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    Message $this$handleMessage_u24lambda_u241 = obtainMessage();
    $this$handleMessage_u24lambda_u241.arg1 = msg.arg1;
    sendMessageDelayed($this$handleMessage_u24lambda_u241, ScanService.SCAN_INTERVAL);
}

I dig into the ScanService class, and find a handler that loops through the External Storage, getting the absolute path and calling a scanFile method that returns a boolean. This boolean is used to classify the object as SAFE or INFECTED.

public final boolean scanFile(File file) {
    Intrinsics.checkNotNullParameter(file, "file");
    try {
        String command = "toybox sha1sum " + file.getAbsolutePath();
        Process process = new ProcessBuilder(new String[0]).command("sh", "-c", command).directory(Environment.getExternalStorageDirectory()).redirectErrorStream(true).start();
        InputStream inputStream = process.getInputStream();
        Intrinsics.checkNotNullExpressionValue(inputStream, "getInputStream(...)");
        Reader inputStreamReader = new InputStreamReader(inputStream, Charsets.UTF_8);
        BufferedReader bufferedReader = inputStreamReader instanceof BufferedReader ? (BufferedReader) inputStreamReader : new BufferedReader(inputStreamReader, 8192);
        try {
            BufferedReader reader = bufferedReader;
            String output = reader.readLine();
            Intrinsics.checkNotNull(output);
            Object fileHash = StringsKt.substringBefore$default(output, "  ", (String) null, 2, (Object) null);
            Unit unit = Unit.INSTANCE;
            CloseableKt.closeFinally(bufferedReader, null);
            return !ScanEngine.KNOWN_MALWARE_SAMPLES.containsValue(fileHash);
        } finally {
        }
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}

The scanFile method on the other hand, runs a command to get the sha1sum of the files, compares it to the hashes known to be ‘INFECTED’ and returns true or false, dependingly. HOWEVER, how the method runs it’s command becomes questionable with possibility of command injection.

The method uses the command toybox sha1sum + 'absolute file path'. For example if the path is, ‘/storage/emulated/0/Pictures/.thumbnails/.nomedia’, the command will be:

toybox sha1sum /storage/emulated/0/Pictures/.thumbnails/.nomedia

Okay, that introduces the possibility for us to create/rename a file with a name that might lead to command injection. For instance, a file named ‘dont scan me; ls’ would lead to the command:

toybox sha1sum dont\ scan\ me; ls

And that means the sha1sum of the file will be calculated then the command ls executed.

Exploitation

To confirm that files I create would be scanned, I create a file ‘hacked;ls’ and enables the scanner. And as I expect, the file is scanned (as SAFE, LoL) and hopefully the command ls was executed.

While our file is scannned, it’s important we confirm that the arbitrary commands are executed. To do that, I create a file named “; touch food.txt”. With this file name in the /storage/emulated/0/Documents directory the command executed by the scanner would be:

toybox sha1sum /storage/emulated/0/Documents/; touch food.txt

That means that a file food.txt would be created. And that is confirmed:

Conclusion

The scanning service exposes a command injection vulnerabilty that if used in even more sophisticated ways could lead to more critical exploits.

Additional Resources


Catch my MHL Series which brings writeups of the Mobile Hacking Lab Android challenges, that are part of the CAPT course. The labs are good for preparation of the CAPT exam.

- - these writeups assume knowledge in basic android hacking methodolgy and familiarity with tools including adb, jadx(gui), Frida, etc