Skip to content
KitploitKITPLOIT
도구익스플로잇블로그
Log in
제출
도구익스플로잇블로그
제출

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
LeakValue — CVE-2022-20452용 익스플로잇: recycle() 후 Parcel을 사용하는 LazyValue를 통해 Android에서 설치된 앱에서 시스템 앱(또는 다른 앱)으로의 권한 상승 | Kitploit
도구/GitHubGitHub/michalbednarski/leakvalue
Android SecurityPrivilege EscalationVulnerability AnalysisExploitationMobile SecurityBinary Exploitation
GitHubmichalbednarski/leakvalue

LeakValue

CVE-2022-20452용 익스플로잇: recycle() 후 Parcel을 사용하는 LazyValue를 통해 Android에서 설치된 앱에서 시스템 앱(또는 다른 앱)으로의 권한 상승

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

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

Android 13은 Parcel 직렬화 메커니즘을 강화하기 위해 많은 개선 사항을 도입합니다.

Android 보안 및 개인정보 보호 팀이 만든 개선 사항에 대한 프레젠테이션은 여기 있습니다

훌륭한 일이며, 많은 취약점을 확실히 제거하거나 악용 불가능하게 만듭니다. 또한 그들은 앱이 다른 앱(시스템 앱 포함)에 자신의 코드를 로드할 수 있게 해주는 이전 익스플로잇을 무력화시키는 방법을 설명합니다.

하지만 이제 저는 같은 결과를 다른 방식으로 달성하는 새로운 익스플로잇을 가지고 돌아왔습니다. 이 익스플로잇은 앞서 언급한 Parcel 강화 과정에서 도입된 다음 취약점에 의존합니다:

  • CVE-2022-20452 (게시판, 패치)
  • CVE-2022-20474 (게시판, 패치)

텍스트를 표시하는 애플리케이션의 스크린샷. 제목: LeakValue. 본문: ValueLeaker-s 6개를 생성했습니다. ActivityTaskManagerService 잠금 중. ActivityTaskManagerService 잠금 완료. ActivityTaskManagerService 잠금 해제 중. ActivityTaskManagerService 잠금 해제 완료. leakedBinders=[android.os.BinderProxy@f06702e]. 유출된 인터페이스: android.app.IApplicationThread. 코드 실행을 요청 중입니다. 셸코드가 다음에서 실행되었습니다: uid=1000 pid=6904 packageName=com.android.settings uid=1000(system) gid=1000(system) groups=1000(system),1007(log),1065(reserved_disk),1077(external_storage),3001(net_bt_admin),3002(net_bt),3003(inet),3007(net_bw_acct),9997(everybody) context=u:r:system_app:s0. 화면 하단에는 두 개의 버튼이 있습니다: START 및 MANUAL TESTING

(앱 실행 중 logcat도 첨부했습니다. 익스플로잇은 로그에서 쉽게 드러납니다)

Parcel 및 Parcelable 불일치 버그 소개

Android의 Parcel 클래스는 프로세스 간 통신의 기초입니다.

객체는 Parcel에 기록할 수 있도록 Parcelable 인터페이스를 구현할 수 있습니다. 예를 들어 (AOSP에서 복사):```java public class UsbAccessory implements Parcelable { public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { public UsbAccessory createFromParcel(Parcel in) { String manufacturer = in.readString(); String model = in.readString(); String description = in.readString(); String version = in.readString(); String uri = in.readString(); IUsbSerialReader serialNumberReader = IUsbSerialReader.Stub.asInterface( in.readStrongBinder());

        return new UsbAccessory(manufacturer, model, description, version, uri,
                serialNumberReader);
    }
};

public void writeToParcel(Parcel parcel, int flags) {
    parcel.writeString(mManufacturer);
    parcel.writeString(mModel);
    parcel.writeString(mDescription);
    parcel.writeString(mVersion);
    parcel.writeString(mUri);
    parcel.writeStrongBinder(mSerialNumberReader.asBinder());

} }

참고로 `Parcel`은 내부적으로 쓰기 또는 읽기가 수행되는 위치를 저장하며, `readString()`은 데이터를 String으로 파싱하는 동시에 위치를 전진시킵니다. 이 위치는 [`dataPosition()`](https://developer.android.com/reference/android/os/Parcel#dataPosition())/[`setDataPosition()`](https://developer.android.com/reference/android/os/Parcel#setDataPosition(int))을 통해 수동으로 가져오거나 설정할 수 있습니다. `Parcelable` 인터페이스의 구현체는 `writeToParcel`과 `createFromParcel`이 동일한 양의 데이터를 쓰고 읽도록 보장해야 합니다. 그렇지 않으면 이후의 모든 읽기가 잘못된 오프셋에서 데이터를 가져오게 됩니다.

[`Bundle`](https://developer.android.com/reference/android/os/Bundle)(프로세스 간에 전송할 수 있는 키-값 맵)에는 [`writeValue()`를 통해 `Parcel`에 쓸 수 있는 다양한 객체](https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:frameworks/base/core/java/android/os/Parcel.java;l=1792-1937)가 포함될 수 있습니다. `Parcel`에서 `Bundle`의 내용을 읽을 때, 해당 시스템에서 사용 가능한 모든 `Parcelable` 클래스를 읽을 수 있습니다.

`Bundle`은 전체 parcel된 데이터의 길이를 `Parcel`에 기록한 다음 [원래 `Parcel`의 관련 부분을 `mParcelledData`에 저장된 보조 `Parcel`로 복사](https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:frameworks/base/core/java/android/os/BaseBundle.java;l=1675-1683)함으로써 실제 내용 파싱을 지연시킵니다. (예를 들어 [`Activity.onSaveInstanceState()`](https://developer.android.com/reference/android/app/Activity#onSaveInstanceState(android.os.Bundle))가 `system_server`에서 사용할 수 없는 `Parcelable`을 제공할 수 있는데, 이 경우 전체 `Bundle`은 내용을 파싱하지 않은 채 그대로 `system_server`에 전달되었다가 다시 돌아옵니다.)

하지만 `Bundle`의 어떤 값에 한 번이라도 접근하게 되면, `Bundle` 내부의 모든 값은 [unparcel되고](https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:frameworks/base/core/java/android/os/BaseBundle.java;l=227-313) [존재하는 모든 키-값 쌍이 파싱됩니다](https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:frameworks/base/core/java/android/os/Parcel.java;l=3613-3632). 그러한 맵에 `writeToParcel`과 `createFromParcel` 메서드가 균형을 이루지 못하는 `Parcelable`이 포함되어 있었고 나중에 해당 `Bundle`이 다른 프로세스로 전달되었다면, 그 다른 프로세스는 `Bundle`의 다른 내용을 볼 수 있었습니다. 시스템에는 `Bundle`이 안전한지 검사된 후 다른 프로세스로 전달되는 [곳](https://cs.android.com/android/platform/superproject/+/android-12.1.0_r8:frameworks/base/services/core/java/com/android/server/accounts/AccountManagerService.java;l=5037-5046)이 있기 때문에, 시스템에서 사용 가능한 클래스들의 [불일치가 시스템 취약점](https://github.com/michalbednarski/ReparcelBug)이 되었습니다.

이 글에서는 하나의 내용을 보여준 후 전달된 뒤에는 다른 내용을 보여주는 이런 `Bundle`을 자가변형 `Bundle`(self-changing `Bundle`)이라고 부르겠습니다.

여기서 또 중요한 점은, `Parcel`은 단순한 바이트(String, 숫자, 위 요소들로 만들어진 객체) 외에도 파일 디스크립터(File Descriptor)와 `Binder`를 포함할 수 있다는 것입니다. `Binder`는 RPC 호출을 할 수 있는 객체입니다. 즉, 한 프로세스가 `Binder` 객체를 만들고 [`onTransact()` 메서드](https://developer.android.com/reference/android/os/Binder#onTransact(int,%20android.os.Parcel,%20android.os.Parcel,%20int))를 재정의합니다. 그런 다음 `Binder`가 다른 프로세스로 전달됩니다. 위 예제 코드에서 `Parcel`에 대해 읽기/쓰기에 사용된 `read`/`writeStrongBinder()` 호출을 볼 수 있습니다. 다른 프로세스에서 `readStrongBinder()`를 사용하면 `BinderProxy` 객체가 생성됩니다([`IBinder` 인터페이스](https://developer.android.com/reference/android/os/IBinder) 뒤에 숨겨져 있습니다). 그러면 그 다른 프로세스는 해당 객체에 대해 [`transact()`](https://developer.android.com/reference/android/os/IBinder#transact(int,%20android.os.Parcel,%20android.os.Parcel,%20int))를 호출할 수 있고, 원래 객체에서는 `onTransact()`가 실행됩니다. 보통은 `transact()`/`onTransact()`를 수동으로 작성하지 않고 [대신 AIDL을 사용](https://developer.android.com/guide/components/aidl)합니다.

# `LazyValue`의 등장, 자가변형 `Bundle`의 종말

과거에는 `writeToParcel`/`createFromParcel` 불일치가 있는 클래스가 많았습니다. Android 13은 [`LazyValue`를 도입](https://android.googlesource.com/platform/frameworks/base/+/9ca6a5e21a1987fd3800a899c1384b22d23b6dee%5E%21/)하여, 시스템 어디에나 존재할 수 있는 그러한 클래스로 인해 자가변형 `Bundle`을 만들 수 있던 문제를 해결합니다.

이제 `writeValue`가 사용될 때, 작성되는 값이 원시 타입(primitive)이 아니면 [값의 길이도 `Parcel`에 기록](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=2331-2344;drc=03c34f57c05feecfb090de3917787f049cb5f804)됩니다.

일반 앱이 `Parcel.readValue()`를 직접 사용하면, `Parcel`에서 읽은 `length`가 실제로 읽은 데이터의 크기와 일치하지 않을 때 경고가 출력된다는 점을 제외하면 [모든 것이 이전과 동일하게 동작](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=4330-4348;drc=03c34f57c05feecfb090de3917787f049cb5f804)합니다. (단, [`Slog.wtfStack`은 절대 예외를 던지지 않는다](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/util/Slog.java;l=108-116;drc=23c7543b8e608ebcbb38b952761b54bb56065577)는 점에 유의하세요.)

하지만 `Bundle`은 이제 대신 [`Parcel.readLazyValue()`](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=4350-4420;drc=03c34f57c05feecfb090de3917787f049cb5f804)를 사용합니다.
도구 다운로드