
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()에 전달됩니다.