구글 픽셀 10 제로 클릭 익스플로잇 체인 분석
보안 연구자들이 구글 픽셀 10 환경에서 사용자의 어떠한 조작(클릭) 없이도 시스템 권한을 획득할 수 있는 제로 클릭(Zero-click) 익스플로잇 체인을 공개했습니다. 기존 픽셀 9에서 사용되던 Dolby 취약점(CVE-2025-54957)을 성공적으로 픽셀 10에 맞게 업데이트했으며, 픽셀 10에 새로 탑재된 VPU(비디오 처리 장치) 드라이버에서 커널 전체 메모리를 제어할 수 있는 치명적인 '로컬 권한 상승(Local Privilege Escalation)' 취약점을 발견했습니다. 이는 안드로이드 기기의 근본적인 보안 아키텍처와 커널 보안에 심각한 위협을 줄 수 있는 매우 중요한 연구 결과입니다.
우리는 최근 구글 픽셀 9에 대한 익스플로잇 체인을 공개하며, 단 두 개의 익스플로잇만으로 제로 클릭(Zero-click) 상황에서 안드로이드 루트(Root) 권한을 획득할 수 있음을 증명했습니다. 이 중 Dolby 제로 클릭 취약점은 2026년 1월에 패치되기 전까지 모든 안드로이드 기기에 존재했습니다. 픽셀 9용 익스플로잇 체인을 보유하고 있었지만, 우리는 픽셀 10에서도 유사한 익스플로잇 체인을 작성할 수 있는지 확인하고 싶었습니다.
Dolby 익스플로잇 업데이트
CVE-2025-54957에 대한 우리의 익스플로잇을 수정하는 것은 꽤 간단했습니다. 필요한 변경 사항의 대부분은 픽셀 9에서 타겟팅했던 특정 라이브러리 버전을 위해 계산된 오프셋(offset)을 픽셀 10의 라이브러리와 유사한 오프셋으로 업데이트하는 것이었습니다. 유일한 어려움(어떤 동기화 프레임(syncframe)에 오프셋이 포함되어 있는지 문서화를 더 잘했어야 했다는 아쉬움을 제외하고)은 픽셀 10이 -fstack-protector 대신 RET PAC을 사용한다는 점이었으며, 이는 __stack_chk_fail을 코드로 덮어쓸 수 없게 만들었습니다. 약간의 시행착오 끝에, 디코더가 초기화될 때 단 한 번만 호출되고 이후에는 다시 사용되지 않아 기능적 문제 없이 덮어쓸 수 있는 초기화 코드인 dap_cpdp_init를 사용했습니다. 업데이트된 Dolby UDC 익스플로잇은 여기에서 확인할 수 있습니다. 이 익스플로잇은 패치되지 않은 기기(SPL 2025년 12월 또는 그 이전)에서만 작동합니다.
BigWave 제거 및 VPU 추가
체인의 로컬 권한 상승(Local Privilege Escalation) 단계를 픽셀 10으로 포팅하는 것은 이 기기에 BigWave 드라이버가 포함되어 있지 않기 때문에 불가능했습니다. 하지만 mediacodec SELinux 컨텍스트의 /dev/vpu에서 새로운 드라이버를 확인할 수 있었습니다. 이 드라이버는 비디오 디코딩 가속을 위해 설계된 Tensor G5 칩의 Chips&Media Wave677DV 실리콘과 상호 작용하는 데 사용됩니다. 오픈 소스 C 파일 내의 주석을 바탕으로, 이 드라이버는 BigWave 드라이버를 구축한 동일한 개발자들이 개발하고 유지 관리하고 있음을 알 수 있었습니다. Jann Horn과 협력하여 이 VPU 드라이버를 2시간 동안 감사(audit)한 결과, 예외적인 취약점을 발견했습니다. (이전 Chips&Media 칩인) WAVE521C용 업스트림 리눅스 드라이버와 달리, WAVE677DV용 픽셀 드라이버는 V4L2(“Video for Linux API”)와 통합되지 않습니다. 대신, 사용자 공간(User space)이 칩의 MMIO 레지스터 인터페이스를 매핑할 수 있도록 허용하는 등 칩의 하드웨어 인터페이스를 사용자 공간에 직접 노출합니다. 이 드라이버는 주로 장치 메모리 매핑을 설정하고, 전원을 관리하며, 사용자 공간이 칩의 인터럽트를 기다릴 수 있도록 합니다.
커널 취약점의 성배(Holy Grail) 이 특정 버그는 악용하기가 예외적으로 간단하여 우리의 주목을 받았습니다.
static int vpu_mmap ( struct file * fp , struct vm_area_struct * vm ) {
unsigned long pfn ;
struct vpu_core * core = container_of ( fp -> f_inode -> i_cdev , struct vpu_core , cdev );
vm_flags_set ( vm , VM_IO | VM_DONTEXPAND | VM_DONTDUMP );
/* This is a CSRs mapping, use pgprot_device */
vm -> vm_page_prot = pgprot_device ( vm -> vm_page_prot );
pfn = core -> paddr >> PAGE_SHIFT ;
return remap_pfn_range ( vm , vm -> vm_start , pfn , vm -> vm_end - vm -> vm_start , vm -> vm_page_prot ) ? - EAGAIN : 0 ;
}
이 mmap 핸들러는 특정 물리적 메모리 주소 범위 내에 있는 VPU 하드웨어의 MMIO 레지스터 영역을 사용자 공간의 가상 주소 공간으로 매핑하기 위한 것입니다. 이 과정에서 이 레지스터 영역의 크기에는 전혀 구애받지 않고 오직 VMA(Virtual Memory Area)의 크기에만 기반하여 remap_pfn_range를 호출합니다. 이는 mmap 시스템 콜에서 레지스터 영역보다 큰 크기를 지정함으로써, 호출자가 VPU 레지스터 영역의 물리적 주소부터 시작하여 원하는 만큼의 물리적 메모리를 사용자 공간으로 매핑할 수 있음을 의미합니다. 커널 이미지 전체(.text 및 .data 영역 포함)는 VPU 레지스터 영역보다 높은 물리적 주소에 위치하므로, 이 버그를 통해 사용자 공간에서 접근하고 수정할 수 있습니다. 이 시점에서 공격자는 단순히 커널 함수를 덮어써서 커널 코드 실행 권한을 얻거나 원하는 어떠한 작업(primitive)이든 수행할 수 있습니다. 커널이 알려진 고정 주소에 위치한다는 사실 덕분에 이러한 공격은 훨씬 더 쉬워집니다.