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