Mobile Hacking Lab - Cyclic Scanner

Android Services Vulnerabilities: A Mobile Hacking Lab CTF Challenge

I got super excited when I saw Mobile Hacking Lab post a new challenge on their platform. I enrolled in the lab immediately and read the instructions and solved it. I didn’t have much time to create the walkthrough but better late than never I guess.

Android Services are components of the Android operating system that perform long-running operations in the background without a user interface. They’re designed to handle operations or perform tasks that should continue even if the user switches to another application. Services can run in the background (background services), work for remote processes (bound services), or execute a specific job that doesn’t require user interaction and stops itself when its task is complete (intent services).

You can access the challenge here: Mobile Hacking Lab - Cyclic Scanner

The Challenge Overview

The objective of this lab is pretty straight forward. Exploit a vulnerability inherent within an Android virus scanner Service to achieve remote code execution (RCE). Let’s boot the application and start the discovery phase.

Discovery Phase

The application is very minimal. There’s a button to turn On or Off the scanner:

We can’t stop the scanner when it’s running. I decided to run pidcat to see what was going on…

System.out:
  I  starting file scan...
  I  /storage/emulated/0/Music/.thumbnails/.database_uuid...SAFE
  I  /storage/emulated/0/Music/.thumbnails/.nomedia...SAFE
  I  /storage/emulated/0/Pictures/.thumbnails/.database_uuid...SAFE
  I  /storage/emulated/0/Pictures/.thumbnails/.nomedia...SAFE
  I  /storage/emulated/0/Movies/.thumbnails/.database_uuid...SAFE
  I  /storage/emulated/0/Movies/.thumbnails/.nomedia...SAFE
  I  finished file scan!

We can see that it’s scanning specific path and mark them as SAFE when no malicious file is detected.

Let’s go through the code with jadx-gui

Code Analysis

The first stop is the ScanEngine class. Let’s go over the interesting bit…

ScanEngine

Constructing the Command

The method constructs a command string to compute the SHA-1 checksum of the file using toybox sha1sum. This command will be executed in a shell, it should be our entry point for RCE. We’ll come back to this later. If you want to jump to the RCE, go straight to the Exploitation section of this article.

    try {
        String command = "toybox sha1sum " + file.getAbsolutePath();

Creating and Starting the Process

A ProcessBuilder is used to create and start a new process that runs the command. The process is configured to use the external storage directory as its working directory and to redirect error streams to the standard output.

        Process process = new ProcessBuilder(new String[0])
            .command("sh", "-c", command)
            .directory(Environment.getExternalStorageDirectory())
            .redirectErrorStream(true)
            .start();

Reading the Process Output

The method retrieves the input stream of the process to read its output. An InputStreamReader wrapped in a BufferedReader is used for read and process text data from a byte stream.

        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);

Processing the Command Output

The method reads the first line of the command’s output, which contains the SHA-1 hash of the file and extracts the hash from the output.

        try {
            String output = bufferedReader.readLine();
            Intrinsics.checkNotNull(output);
            
            Object fileHash = StringsKt.substringBefore$default(output, "  ", (String) null, 2, (Object) null);
            Unit unit = Unit.INSTANCE;

Checking Against Known Malware Samples

The extracted SHA-1 hash is checked against a known list of malware samples (ScanEngine.KNOWN_MALWARE_SAMPLES). If the hash is found in the list, the file is considered unsafe, and the method returns false. Otherwise, it returns true.

            return !ScanEngine.KNOWN_MALWARE_SAMPLES.containsValue(fileHash);
        } finally {
        }
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}

Now since the method check whether a given file is safe or malicious against a list of known hashes, we have to know which known hash exist. We can see those hashes in the static final HashMap

Let’s check the Known Malware Samples…

Defining Known Malware Samples

This static final HashMap named KNOWN_MALWARE_SAMPLES map stores pairs of file names and their corresponding SHA-1 hashes. The MapsKt.hashMapOf function is used to create the map, and TuplesKt.to is used to create the key-value pairs and their corresponding hashes.

If a file contains a name and hash from the list, it will be flag as INFECTED

private static final HashMap<String, String> KNOWN_MALWARE_SAMPLES = MapsKt.hashMapOf(
    TuplesKt.to("eicar.com", "3395856ce81f2b7382dee72602f798b642f14140"),
    TuplesKt.to("eicar.com.txt", "3395856ce81f2b7382dee72602f798b642f14140"),
    TuplesKt.to("eicar_com.zip", "d27265074c9eac2e2122ed69294dbc4d7cce9141"),
    TuplesKt.to("eicarcom2.zip", "bec1b52d350d721c7e22a6d4bb0a92909893a3ae")
);

Why INFECTED? we can see in the code below (took from the ScanService Class)

ScanEngine

                System.out.print((Object) (file.getAbsolutePath() + "..."));
                boolean safe = ScanEngine.INSTANCE.scanFile(file);
                System.out.println((Object) (safe ? "SAFE" : "INFECTED"));
            }
        }

The method print the file’s absolute path and then uses ScanEngine.INSTANCE.scanFile(file) to scan the file. It prints SAFE if the file is safe (as seen in the pidcat output!) and INFECTED if it is not.

Exploitation

Let’s start with adding a file called eicar.txt containing the eicard string and monitor the log using pidcat. If things go according to plans, we should see the INFECTED displayed in the logs.

eicar string: X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*

As expected, we can see INFECTED with the full path of the file:

How can we achieve RCE ?

Now this is fairly simple, let’s go over the code again from the ScanEngine class:

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();

The command string is dynamically constructed by concatenating "toybox sha1sum " with file.getAbsolutePath(). This allows any value returned by file.getAbsolutePath() to be included directly in the command string…

If file.getAbsolutePath() returns a path that contains shell metacharacters (;, &&, |), it can change the intended command execution (toybox sha1sum).

The ProcessBuilder is set to run the command in a shell (sh -c). The shell interprets and executes the command string, allowing shell metacharacters to be processed… which turns into a command injection!

It’s a matter of how we can name the file…

If the file path includes "; malicious command", the command will be split and execute both toybox sha1sum and malicious command just like a traditional operator.

Let’s create an empty txt file and our payload will be in the filename:

; echo 'RCE PoC from almightysec!!' > almightysec.txt #

The ; allows injection of the payload: echo 'RCE PoC from almightysec!!' > almightysec.txt, which creates a file with the content we echo. I added # to make sure trailing commands interfere.

Let’s run it!

We push the file on the device in /sdcard/Download since that’s where the scanner looks for malicious files.

adb push \;\ id\ \&\ echo\ \'RCE\ PoC\ from\ almightysec\!\!\'\ \>\ almightysec.txt\ \# /sdcard/Download/

Our file is now located in /sdcard/Download:

We can monitor the logs using pidcat and we can see that our file is now marked as SAFE. We can also see that the exploit worked because the malicious file almightysec.txt exist in /storage/emulated/0/!

We can confirm the content of the file by getting a shell on the device:

Conclusion

Another exciting challenge conquered at Mobile Hacking Lab!

The “Cyclic Scanner” centered on an Android virus scanner service with a critical flaw that allowed remote code execution (RCE). Imagine the implications of an attacker gaining such control over your device…

Just looking at the service’s behavior and understanding the code, we crafted a specific payload to exploit the dynamically constructed command strings. Never trust user input!!

Let’s move on to the next challenge!

Thank you, Mobile Hacking Lab! 😊