
CVE-2024-34740에 대한 분석 및 익스플로잇: Android의 BinaryXmlSerializer에서 정수 오버플로우를 이용해 system_server 파일 쓰기로 이어지고, 이후 일반 설치 앱에서 system_server 코드 실행까지 가능

여기에 설명된 문제에 대한 수정 사항은 CVE-2024-34740 / A-307288067로 나타났습니다:
Android system_server 내부에서 많은 서비스들이 재부팅 후에도 상태를 유지하기 위해 XML 파일에 저장합니다.```
$ adb shell su 0 find /data/system -name '*.xml' | sort
/data/system/appops_accesses.xml
/data/system/cachequota.xml
/data/system/device_policies.xml
/data/system/device_policy_state.xml
/data/system/display-manager-state.xml
/data/system/input-manager-state.xml
/data/system/inputmethod/subtypes.xml
/data/system/install_sessions.xml
/data/system/job/jobs_1000.xml
/data/system/job/jobs_10131.xml
/data/system/log-files.xml
/data/system/netpolicy.xml
/data/system/notification_policy.xml
/data/system/overlays.xml
/data/system/packages.xml
/data/system/package-watchdog.xml
/data/system/sensor_privacy_impl.xml
/data/system/sensor_privacy.xml
/data/system/shortcut_service.xml
/data/system/users/0/app_idle_stats.xml
/data/system/users/0/appwidgets.xml
/data/system/users/0/package-restrictions.xml
/data/system/users/0/settings_global.xml
/data/system/users/0/settings_secure.xml
/data/system/users/0/settings_system.xml
/data/system/users/0/wallpaper_info.xml
/data/system/users/0.xml
/data/system/users/userlist.xml
/data/system/watchlist_settings.xml
역사적으로 이러한 파일은 일반 텍스트 XML 파일로서 들여쓰기가 되어 있어 개발자가 쉽게 읽을 수 있었지만, [Android 12에서는 해당 형식의 새로운 바이너리 버전이 도입되었는데, `system_server`가 소비하는 전체 시간의 1.5%가 이러한 XML 작업에 사용된다는 점을 근거로 들었습니다](https://android.googlesource.com/platform/frameworks/base/+/4ccea8796991d678ead4399130ec31edf63ff4fa%5E%21/)
이 형식은 시스템 내부에서만 사용되며 매직 값 `"ABX\x00"`을 가진 파일이라는 점에 유의해야 합니다. 이는 `AndroidManifest.xml`, `res/xml/*.xml`, `res/layout/*.xml` 등에 사용되는 [APK 내부 형식](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/androidfw/ResourceTypes.cpp;l=1770;drc=d4e49e63519397789d284a03aea5fafc119cb1b0)과 다릅니다. APK 내부 형식에는 명시적인 "매직 값"이 없지만, 일반적으로 `0300 0800`으로 시작합니다 (이는 `type=RES_XML_TYPE`, `headerSize=8`인 [헤더](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/libs/androidfw/include/androidfw/ResourceTypes.h;l=608;drc=d4e49e63519397789d284a03aea5fafc119cb1b0)입니다).
시스템이 이러한 내부 상태 XML 파일 중 하나를 읽을 때마다 파일의 매직 값 `"ABX\0"`을 사용하여 [Binary XML 파일용 파서와 일반 XML 파서 중 하나를 선택합니다](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/Xml.java;l=188-192;drc=97a370a95275e79c69e79d7ead11aa38934a5575). 이러한 파일이 Binary XML로 저장되는지 여부는 [시스템 속성](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/util/Xml.java;drc=97a370a95275e79c69e79d7ead11aa38934a5575;l=74?q=Xml.java)에 의해 제어되며 기본적으로 활성화되어 있습니다.
Binary XML 파일이 사용 중일 때, 예를 들어 `adb shell su 0 abx2xml /data/system/packages.xml -` 명령어를 통해 그 내용을 읽을 수 있습니다.
이 바이너리 형식이 제공하는 기능 중 하나는 타입화된 접근자를 제공한다는 것입니다. 따라서 직렬화기는 `attributeInt(String namespace, String name, int value)` 메서드를 제공하며, 이 메서드는 값을 바이너리 정수로 작성하여 문자열을 통한 왕복(round-trip)을 피합니다. 문자열 왕복은 새로운 할당과 이후 가비지 컬렉션 대상 객체를 생성하게 됩니다.
직접 직렬화할 수 있는 또 다른 타입은 바이트 배열입니다.```java
@Override
public XmlSerializer attributeBytesBase64(String namespace, String name, byte[] value)
throws IOException {
if (namespace != null && !namespace.isEmpty()) throw illegalNamespace();
mOut.writeByte(ATTRIBUTE | TYPE_BYTES_BASE64);
mOut.writeInternedUTF(name);
mOut.writeShort(value.length);
mOut.write(value);
return this;
}
마찬가지로 TYPE_* 태그만 다른 attributeBytesHex 메서드도 있습니다. 해당 태그는 abx2xml 도구가 바이트 배열을 적절한 문자열 표현으로 변환하는 데 사용됩니다.
mOut은 FastDataOutput의 인스턴스이며, Java의 DataOutputStream 기능을 제공합니다. writeByte/writeShort/writeInt/writeUTF/write는 표준 DataOutputStream과 동일한 형식을 사용합니다.
Parcel과 유사하게 쓰기/읽기 중에 불일치가 발생하면 이후 읽는 데이터가 잘못된 오프셋에서 가져오게 됩니다. 그러나 Parcel과 달리 BinaryXmlSerializer/BinaryXmlPullParser 사용 실수는 공격자에게 읽기 데이터를 임의로 변조할 수 있는 능력을 주지 않습니다(이 경우 공격자가 새로운 태그/속성 이름/값을 도입할 수 없습니다).
그러나 BinaryXmlSerializer 클래스 자체나 FastDataOutput 내의 실수는 그렇습니다.
위 메서드에서 길이가 65536인 바이트 배열을 쓰려고 하면 writeShort()로 길이를 쓰게 되는데, 이는 사실상 0을 쓰게 되고 그 후 실제 배열 내용이 기록됩니다.
이러한 불일치를 악용하려면 attributeBytesBase64 또는 attributeBytesHex에 임의 바이트 배열을 삽입할 수 있고, 해당 파일의 수정이 공격자에게 가치가 있는 일부 파일을 선택해야 합니다.
PackageInstaller 클래스는 설치를 위한 패키지를 준비하는 기능을 제공합니다. 어떤 권한도 필요 없이, 모든 앱은 임시 디렉터리에 설치할 새 APK를 쓸 수 있습니다. 설치에 필요한 모든 것을 작성한 후 설치 앱은 commit() PackageInstaller.Session을 호출할 수 있으며, 이는 설치 파일에 더 이상 변경을 가할 수 없고 Session은 사용자 승인 또는 실제 설치를 위해 준비됨을 의미합니다.
이러한 작업의 상태는 /data/system/install_sessions.xml에 저장됩니다. 설치 앱은 예를 들어 Package Manager Service가 PackageInstaller.Session을 위해 생성한 임시 디렉터리에 대용량 APK의 절반을 다운로드한 후, 재부팅 후 다운로드를 재개하고 나머지 절반을 작성하여 설치를 커밋할 수 있습니다.
한 가지 가능성은 install_sessions.xml에 데이터를 기록하여 세션을 staged(스테이징) 상태로 표시하는 것입니다. 이는 다음 부팅 후 설치됨을 의미합니다.
또 다른 가능성은 여기서 제시된 것으로, 설치 파일이 준비되는 임시 디렉터리의 경로를 변경하는 것입니다. openWrite()/openRead()는 경로 탐색이 없는 한 모든 유효한 파일 이름을 허용하고 해당 파일을 stageDir 필드가 가리키는 디렉터리에 배치하며, 이 stageDir는 XML에서 읽어옵니다.
이제 우리가 제어하는 바이트 배열을 실제로 attributeBytesBase64()에 전달해야 합니다.
PackageInstaller.Session은 setChecksums() 메서드를 제공합니다.
system_server 측에서 제공된 Checksum은 호출자가 제공한 서명에 대해 선택적으로 확인된 후 mChecksums에 저장됩니다.
install_sessions.xml이 기록될 때 checksum.getValue()가 writeByteArrayAttribute에 전달되고, 이는 다시 attributeBytesBase64()에 전달됩니다.
install_sessions.xml의 쓰기를 트리거하는 몇 가지 이벤트가 있으며, 그 중 하나는 새 Session 생성입니다. 따라서 이 익스플로잇은 한 세션에 Checksum을 설정한 후 첫 번째 Session이 파일에 저장되었는지 확인하기 위해 새 Session을 생성합니다.
이제 길이가 65536인 바이트 배열을 작성하면, 읽을 때 크기가 0으로 해석되고 해당 배열의 내용이 BinaryXmlPullParser가 파싱하는 원시 데이터가 됩니다.
속성 개수는 지정되지 않았습니다. 각 항목에는 token을 포함하는 태그 바이트가 있습니다. 하위 니블에는 XmlPullParser에 정의된 START_TAG, END_TAG 또는 END_DOCUMENT와 같은 이벤트 유형 중 하나가 있습니다. 이러한 유형 외에도 특수 ATTRIBUTE 유형이 있으며, 이는 next()를 통해 보고되지 않지만 START_TAG 토큰을 본 후 파서가 ATTRIBUTE가 아닌 토큰을 볼 때까지 다음 토큰을 미리 봅니다.
속성 개수가 지정되지 않았으므로 END_TAG 토큰을 통해 현재 요소를 즉시 닫을 수 있습니다. 그런 다음 </session>도 닫습니다. 모든 중요한 요소 속성은 <session> 시작 태그에 있지만 우리는 그 지점을 지났습니다. 그러나 이제 새 <session> 요소를 열고 거기에 속성을 설정할 수 있습니다.
위에서 언급했듯이 FastDataInput은 Java의 DataInputStream과 호환되지만, 추가로 readInternedUTF() 메서드가 있어 이전 문자열을 참조할 수 있습니다. 이전에 어떤 문자열이 인터닝되었는지 알 수 없으므로 항상 이전에 보지 못한 문자열이 기록되었다고 지정합니다. 이렇게 하면 새로 읽은 문자열이 풀에 추가되어 삽입 지점 이후에 기록된 데이터를 읽는 데 문제가 발생할 수 있습니다. 그러나 삽입의 일부로 모든 종료 태그와 END_DOCUMENT 토큰을 삽입하므로 삽입 후에는 해당 파일에서 더 이상 아무것도 읽지 않습니다.
stageDir을 사용한 PackageInstaller.Session 활용시스템이 수정된 install_sessions.xml을 읽으면 stageDir이 우리가 제어하는 값으로 설정된 PackageInstallerSession 객체를 얻습니다.
처음 생각은 stageDir을 /proc/self로 설정하고 maps를 읽고 mem에 쓰는 것이었지만 작동하지 않았습니다.
openRead()를 사용하여 /proc/self/maps를 열려고 시도했을 때 system_server는 파일을 성공적으로 열었지만, Binder를 통해 untrusted_app에 해당 파일을 전달하는 것은 SELinux에 의해 차단되었습니다.
그러나 쓰기는 원시 파일 디스크립터를 다른 프로세스에 전달하는 것이 아니라 system_server를 통해 프록시됩니다. 세션이 커밋되면 system_server가 쓰기 액세스를 취소할 수 있어야 하기 때문입니다. 그렇다면 /proc/self/mem에 쓸 수 있다는 뜻일까요? system_server가 해당 파일을 열 수는 있지만, 무언가를 쓰기 전에 해당 파일에 대해 Os.chmod()를 호출하는데, /proc/self/mem에서는 그렇게 할 수 없습니다. 따라서 여기서는 이를 악용에 사용할 수 없습니다. 하지만 그 외에는 system_server가 해당 파일을 열고 우리가 지정한 오프셋에 쓰기를 수행할 수 있으며, 해당 파일은 코드 페이지 덮어쓰기를 허용하여 직접 코드 실행을 제공할 수 있습니다.
그 옵션이 불가능하므로 다음 아이디어를 시도했습니다. /data/system/packages.xml의 내용을 교체하는 것입니다. 이 파일은 PackageManagerService의 상태, 특히 어떤 앱이 설치되었고 어떤 uid가 할당되었는지에 대한 정보를 포함합니다.
system_server가 해당 파일에 직접 쓸 수 없는 것으로 보입니다. 대신 시스템이 해당 파일을 쓸 때마다 먼저 임시 파일에 쓴 다음 임시 파일로 packages.xml을 교체하고 해당 파일에 보호를 활성화합니다.
그러나 /data/system/packages.xml을 읽을 때 시스템은 먼저 /data/system/packages-backup.xml 파일이 있는지 확인하고, 있으면 기본 packages.xml이 손상된 것으로 간주하고 대신 백업을 읽습니다. 정상 작동 중에는 /data/system/packages-backup.xml 파일이 존재하지 않으며, stageDir을 /data/system으로 설정한 조작된 PackageInstallerSession을 사용하여 생성할 수 있습니다.
또한 system_server는 openRead()를 사용할 때 /data/system/packages.xml의 읽기 전용 파일 디스크립터를 보낼 수 있으므로, 이전 내용을 손상시키지 않고 수정 사항만 포함하는 패치된 파일을 쉽게 빌드할 수 있습니다.
sharedUserId="android.uid.system" 액세스 권한 부여packages.xml에는 설치된 애플리케이션의 정의가 등록되어 있습니다. 예를 들어:```xml
<package name="com.android.settings" codePath="/system_ext/priv-app/Settings" ... sharedUserId="1000" ...>
/data/app 디렉토리의 어딘가에 (다른 `PackageInstallerSession`을 사용하여) 새로운 APK를 작성하고, `packages.xml`에 새로운 `<package>` 요소를 추가한 후 해당 방식으로 설치할 수 있을까요?
네, 그러나 `<cert>`에 새로 설치된 APK의 유효한 서명을 제공해야 하며, 시스템은 부팅 중에 이를 APK 파일과 대조하여 확인합니다.
`userId` 속성을 원하는 값으로 설정할 수 있을까요? (`AndroidManifest.xml`에 `<manifest android:sharedUserId>` 속성이 없는 APK를 나타내기 위해 `sharedUserId` 대신 사용)
네, 그러나 다른 패키지나 `sharedUserId`에서 이미 사용 중인 값은 사용하지 않아야 합니다.
앱에 대해 `sharedUserId="1000"`을 설정할 수 있을까요?
그렇게 하면 부팅 중 시스템이 [`canJoinSharedUserId()`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java;l=658-750;drc=7ec13b04c3bbaeac99cbbc4db9f9f80492c508fe)를 통해 해당 설정을 검증합니다.
특히 해당 메소드는 [`checkCapability()`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/content/pm/SigningDetails.java;l=613-637;drc=97a370a95275e79c69e79d7ead11aa38934a5575)를 사용하여 서명이 정확히 일치하는지, 또는 한쪽의 서명이 다른 쪽의 과거 서명 중 하나와 일치하는지 확인합니다.
이러한 '과거 서명'은 `packages.xml`에서 비롯됩니다. 특히 `<sigs>` 요소에 `<cert>`가 있을 때, `<sigs>` 아래에 `<pastSigs>` 요소를 추가하여 [`SigningDetails.mPastSigningCertificates`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/content/pm/SigningDetails.java;l=74-87;drc=97a370a95275e79c69e79d7ead11aa38934a5575)에 새 항목을 추가할 수 있습니다.
결국, 조작된 `<shared-user>` 요소는 다음과 같습니다:```xml
<shared-user name="android.uid.system" userId="1000">
<sigs count="1" schemeVersion="3">
<cert index="3" />
<pastSigs count="2" schemeVersion="3">
<cert index="19" flags="2" />
<cert index="19" flags="2" />
</pastSigs>
</sigs>
</shared-user>
<cert> 요소가 <pastSigs> 아래에 두 번 삽입되는 이유는 마지막 과거 서명이 현재로 간주되어 고려되지 않기 때문입니다
flags="2"는 인증서가 sharedUserId에 허용됨을 의미합니다
또한 <package sharedUserId="1000"> 등록은 android:sharedUserId="android.uid.system"을 매니페스트에 선언한 앱에 적용되어야 하므로, 익스플로잇을 수행하는 앱과 별도의 APK여야 합니다
android:sharedUserId="android.uid.system"에 대해 신뢰할 수 있는 새로운 인증서를 등록할 수 있었지만, 일반적으로 해당 인증서로 서명되고 매니페스트에 sharedUserId만 선언한 앱은 시작할 수 없습니다. 실행하면 logcat에 다음과 같은 메시지가 표시됩니다:```
signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
Abort message: 'JNI FatalError called: (com.example.abxoverflow.droppedapk) frameworks/base/core/jni/com_android_internal_os_Zygote.cpp:1976: selinux_android_setcontext(1000, 0, "default:privapp:targetSdkVersion=33:complete", "com.example.abxoverflow.droppedapk") failed'
이는 [`seapp_contexts` 파일](https://cs.android.com/android/platform/superproject/main/+/main:system/sepolicy/private/seapp_contexts)의 정의 중 어느 것도 일치하지 않았기 때문입니다.
해당 파일의 `user=` 규칙은 [`uid`](https://cs.android.com/android/platform/superproject/main/+/main:external/selinux/libselinux/src/android/android_seapp.c;l=819-833;drc=530165a996d8ca5ab5959c33bc040c78951bcb59) (`selinux_android_setcontext()`의 첫 번째 인수)에서 매핑되며, 우리의 경우 `user=system`이 되고, 일반 앱의 경우 `user=_app`입니다.
일치시킬 다른 항목은 `seinfo=` 규칙으로, 이는 `selinux_android_setcontext()`의 세 번째 인수에서 첫 번째 콜론까지 가져옵니다. 원래 이 값은 실행된 앱의 서명을 `/system/etc/selinux/plat_mac_permissions.xml`에 정의된 것과 비교하여 생성됩니다.
결국 우리 앱은 `user=system seinfo=default`와 일치하려고 시도하지만 `seapp_contexts`에는 그러한 규칙이 없습니다.
하지만, 새로운 `android:sharedUserId="android.uid.system"` 앱의 프로세스를 시작할 수는 없지만, [`android:process` 속성](https://developer.android.com/guide/topics/manifest/application-element#proc)을 통해 지정하면 앱을 기존 프로세스에 로드할 수 있습니다. 특히, `android:uid.system`에서 실행되는 앱은 `android:process="system"`을 지정하여 `system_server`에 로드될 수 있습니다.
# 시스템 충돌
일반적으로 [앱이 `system_server`의 충돌을 유발하는 것은 Negligible Security Impact의 버그로 간주](https://bughunters.google.com/learn/invalid-reports/android-platform/5148417640366080/bugs-with-negligible-security-impact#triggering-a-local-temporary-denial-of-service)되며, 여기서는 두 번의 `system_server` 재시작이 필요한 익스플로잇 체인의 일부라는 점만 주목할 만합니다.
어쨌든, 우리는 `Parcelable` 체인을 얻었습니다:
* [`IAlarmManager.set()` AIDL 메서드는 `AlarmManager.AlarmClockInfo`를 허용합니다](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/apex/jobscheduler/framework/java/android/app/IAlarmManager.aidl;l=32-35;drc=ca41ed611ac9c6584c6d5c38ae8428b8e4f3b135)
* [`AlarmClockInfo`는 유형 인수 없이 더 이상 사용되지 않는 `readParcelable()`을 호출합니다](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/apex/jobscheduler/framework/java/android/app/AlarmManager.java;l=1598;drc=04bf84e220ade9d7ad8ef0b2f7e6ce6ec72841c8) (apex 모듈에 있어 새 메서드로 전환되지 않았기 때문)
* `Parcelable` 클래스로 [`android.content.pm.PackageParser$Activity`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/content/pm/PackageParser.java;l=8240;drc=7d3ffbae618e9e728644a96647ed709bf39ae759)를 지정합니다.
* 이를 읽으면 [단일 `Parcel` 인수를 받는 모든 public 생성자가 호출됩니다](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/content/pm/PackageParser.java;l=7789-7795;drc=7d3ffbae618e9e728644a96647ed709bf39ae759).
* [`android.os.PooledStringWriter`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/os/PooledStringWriter.java;l=55;drc=782d49826862cbdc9d020fc9d85f8a6f64675dcb)를 지정하면 제공된 `Parcel`에 대해 `writeInt(0)`을 호출합니다.
* 그 `writeInt()` 호출은 [`onTransact()`](https://developer.android.com/reference/android/os/Binder#onTransact(int,%20android.os.Parcel,%20android.os.Parcel,%20int))의 `data` 인수로 받은 `Parcel`에 대해 수행되었으며, 이는 `/dev/binder`에서 `mmap`된 읽기 전용 메모리로 백업됩니다. 해당 쓰기는 `SIGSEGV`를 발생시킵니다.
또한 주목할 점은, [이전 보고서(예: CVE-2023-21098)에서도 `PackageParser`+`PooledStringWriter` 조합을 사용했습니다](https://github.com/michalbednarski/TheLastBundleMismatch).
# 전체 흐름
앱 내에서 "Do everything" 버튼을 누르면 발생하는 일입니다.
1. `RebootBackgroundRunner`가 별도의 프로세스로 시작되며, 이제 [`setsid()`](https://man7.org/linux/man-pages/man2/setsid.2.html)를 사용하여 사용자 공간 재부팅에서 살아남은 후 백그라운드에서 대기합니다.
2. 새로운 `PackageInstaller.Session`이 할당되고 새 `Checksum` 객체가 추가됩니다. 해당 `Checksum` 객체는 직렬화 중에 정수 오버플로우를 일으킬 크기의 바이트 배열을 포함하며, 데이터가 역직렬화되면 시스템은 이전에 `Checksum` 페이로드였던 데이터를 가진 `PackageInstaller.Session`을 보게 됩니다. 특히, 두 개의 세션이 주입됩니다.
* `sessionStageDir="/data/system"` 및 `prepared="true"`인 세션 (스테이지 디렉터리가 이미 준비되어 있어 생성할 필요 없음)
* `sessionStageDir="/data/app/dropped_apk"` 및 `prepared="false"`인 세션 (첫 번째 `Session.openWrite()` 호출 시 디렉터리가 생성됨)
3. 새로운 `PackageInstaller.Session`이 할당된 후 즉시 파괴됩니다. 이로 인해 시스템이 업데이트된 내용을 `install_sessions.xml`에 기록합니다.
4. 약간의 지연 후 `system_server` 충돌이 트리거됩니다.
5. 다음 `system_server` 시작 중에 `install_sessions.xml` 파일이 읽히고, 우리가 주입한 `PackageInstaller.Session`을 사용할 수 있게 됩니다.
6. `RebootBackgroundRunner`는 사용자 공간 재부팅 동안 백그라운드에서 대기하고 있으며, 시스템이 다시 작동하고 준비되었음을 감지하면 다음 단계를 수행합니다.
7. 하나의 `PackageInstaller.Session`을 사용하여 에셋에서 새 APK를 추출하고 `/data/app/dropped_apk/base.apk`에 씁니다.
8. 다른 세션을 사용하여 `/data/system/packages.xml`을 읽고, 해당 파일을 패치하여 새로 드롭된 APK가 이미 설치되었으며, 이에 사용된 인증서가 이전에 `android:sharedUserId="android.uid.system"`에 사용되었고 여전히 해당 목적으로 신뢰됨을 선언합니다. 변경된 파일은 `/data/system/packages-backup.xml`로 작성됩니다.
9. 또 다른 `system_server` 충돌이 트리거됩니다.
10. `system_server`가 시작 시 `packages-backup.xml`을 보면 원래 `packages.xml`이 손상된 것으로 간주하고 백업을 사용합니다.
11. 시스템이 수정된 `packages.xml`을 읽었으므로, 방금 드롭된 앱이 존재하며 [`ACTION_BOOT_COMPLETED`](https://developer.android.com/reference/android/content/Intent#ACTION_BOOT_COMPLETED)에서 자체적으로 실행됩니다. 해당 새 앱은 `system_server` 내에서 실행되는데, 이는 `AndroidManifest.xml`에 `<manifest android:sharedUserId="android.uid.system">` 및 `<application android:process="system">`이 있기 때문입니다.
# `utils/`의 스크립트
PoC 앱과 함께 `utils` 디렉터리에 몇 가지 스크립트가 있습니다.
* `moveapk.sh`: 컴파일된 APK를 드로퍼의 `assets`에 넣기 위해 이동합니다. `gradle :droppedapk:assembleRelease` 후에 실행합니다.
* `peeksessions.sh`: `install_sessions.xml`의 현재 내용을 볼 수 있습니다 (Android의 `eng`/`userdebug` 빌드 필요).
* `wipesessions.sh`: 현재 존재하는 `PackageInstaller.Session`을 모두 지우고 시스템을 재시작합니다 (Android의 `eng`/`userdebug` 빌드 필요).
# 잡학
관련된지는 확실하지 않지만, ABX 관련 버그 (`cd frameworks/base ; git log -S ABX`)의 이력을 살펴보면 ["Stop processing on IOException" 커밋](https://android.googlesource.com/platform/frameworks/base/+/5112cfef2a2023a2629a426154547444593e9f9b%5E!/)을 발견했습니다. 이 커밋은 **잘린 ABX 파일이 포함된 단위 테스트 추가를 포함합니다**. 해당 커밋은 ["Ignore malformed shortcuts"](https://android.googlesource.com/platform/frameworks/base/+/d5122bfaf18f1503e73c1a3a177a56d0f604a008%5E%21/)의 후속 조치였으며, 이는 [게시판에서 DoS로 설명되었습니다](https://source.android.com/docs/security/bulletin/2022-12-01#framework).