
empty_list - p0 이슈 1564 (CVE-2018-4243) iOS 11.0 - 11.3.1 커널 r/w 익스플로잇
empty_list - p0 이슈 1564 (CVE-2018-4243) iOS 11.0 - 11.3.1 커널 읽기/쓰기 익스플로잇 @i41nbeer
버그: getvolattrlist은 fgetattrlist 시스템 콜을 통해 사용자 제어 bufferSize 인자를 받습니다. 속성 목록을 직렬화할 커널 버퍼를 할당할 때 다음과 같은 주석이 있습니다:
/*
문제는 사용자가 제공한 버퍼 크기가 요청된 헤더 크기보다 작은 경우를 코드가 올바르게 처리하지 않는다는 것입니다. ATTR_CMN_RETURNED_ATTRS를 전달하면 다음 코드에 도달하게 됩니다:
/* 요청된 경우 속성 집합 출력을 반환합니다. / if (return_valid) { ab.actual.commonattr |= ATTR_CMN_RETURNED_ATTRS; if (pack_invalid) { / 유효한 속성만 보고 */ ab.actual.commonattr &= ab.valid.commonattr; ab.actual.volattr &= ab.valid.volattr; } bcopy(&ab.actual, ab.base + sizeof(uint32_t), sizeof (ab.actual)); }
할당된 버퍼가 최소한 그 크기를 수용할 수 있을 만큼 충분한지에 대한 검사가 없습니다.
익스플로잇: 이에 대한 더 긴 형식의 글을 게시할 예정이며, 다음은 익스플로잇 작동 방식에 대한 대략적인 노트입니다:
이 버그는 kalloc.16 할당의 끝에서 8바이트의 0을 쓸 수 있는 기능을 제공합니다. 그 바이트에서 몇 비트를 제어할 수 있을 것처럼 보이지만 실제로는 그렇지 않다고 생각되어 NULL 포인터를 끝에 쓰는 것처럼 익스플로잇에 집중했습니다.
이것은 매우 제한된 프리미티브이므로 첫 번째 단계는 가능한 것들을 열거하는 것입니다:
결국 첫 번째 옵션을 선택했습니다. 그런 다음 두 가지 추가 요구 사항이 있습니다:
struct ipc_port를 대상으로 선택했습니다. 이 구조체는 두 번째 dword에 참조 횟수 필드가 있어 첫 번째 요구 사항을 충족합니다. 그러나 kalloc.16에 할당되지 않으며 자체 영역(ipc_ports)에 있습니다.
이는 kalloc.16 영역 블록을 ipc_ports 블록 바로 앞에 정렬한 다음, kalloc.16 블록의 마지막 kalloc.16 할당에서 ipc_ports의 첫 번째 할당으로 오버플로해야 함을 의미합니다.
이를 더 쉽게 만들기 위해 두 가지 트릭을 사용할 수 있습니다:
프리리스트 역전: 영역 할당은 먼저 중간(부분적으로 가득 찬) 페이지에서 이루어집니다. 이는 그룹 중간 어딘가에서 k.16 객체를 해제하고 할당하기 시작하면 현재 중간 페이지가 가득 차거나 비워질 때까지 재사용되지 않음을 의미합니다.
이는 새로운 페이지의 프리리스트가 반쯤 무작위로 채워져 할당이 안쪽에서 바깥쪽으로 진행되기 때문에 도전 과제를 제공합니다:
| 9 8 6 5 2 1 3 4 7 10 | <-- 새로 비워진 페이지의 예시 "무작위화된" 할당 순서
이는 최종 중간 k.16 및 포트 페이지가 다음과 같이 보이게 됨을 의미합니다:
| - - - 5 2 1 3 4 - - | - - - 4 1 2 3 5 - - | kalloc.16 ipc_ports
오버플로를 사용하여 프리리스트 항목을 손상시키면 할당 시 패닉이 발생하므로 이를 피해야 합니다.
트릭은 할당 및 해제 순서를 제어하여 프리리스트를 역전시켜 최종 중간 페이지가 다음과 같이 보이게 하는 것입니다: | 1 4 - - - - - 5 3 2 | 2 5 - - - - - 4 3 1 | kalloc.16 ipc_ports
이 시점에서 kalloc.16을 해제하고 오버플로를 위해 재할당하여 ipc_port의 첫 번째 qword에 도달할 가능성이 훨씬 높아집니다.
안전하게 오버플로 가능한 할당: 대상이 되는 할당(ipc_port 직전의 맨 끝)에 도달하기 전에 오버플로해야 할 후보 할당이 많을 것이므로, kalloc.16 페이지에 할당된 객체가 NULL 포인터로 손상되어도 안전하도록 해야 합니다.
이를 위해 mach 메시지 ool_port 설명자를 사용합니다. NULL은 유효한 값이기 때문입니다.
익스플로잇 흐름: kalloc.16 프리리스트를 역전시키기 위해 그룹을 수행하고 ipc_port로 오버플로를 시도하기 시작합니다.
손상될 포트를 포함하는 mach 포트 이름의 대략적인 범위를 알고 있습니다. 각 오버플로 시도 후 이러한 포트 각각을 확인하여 포트가 손상되었는지 확인합니다. 성공적인 손상의 부작용은 포트의 io_active 플래그가 0으로 설정된다는 것입니다. mach_port_kobject MIG 메서드를 사용하여 부작용 없이 이를 감지할 수 있습니다.
손상된 포트를 찾으면 참조를 가져오고 해제해야 합니다. 더 중요한 것은 io_active 플래그를 확인하지 않는 코드 경로가 필요하다는 것입니다. mach_port_set_attributes가 이를 수행합니다.
이제 kalloc.16 끝의 NULL 포인터 쓰기를 댕글링 mach 포트로 변환했습니다 :)
영역 가비지 컬렉션을 유발하여 포트의 메모리가 kalloc.4096 페이지로 재사용되도록 합니다. 먼저 ool_ports 설명자로 재사용되도록 하여 ip_context 필드가 카나리 포트로 보내는 send right와 겹치게 합니다. 이를 통해 커널 내 객체의 대략적인 주소를 알 수 있습니다. 그런 다음 ool_desc를 파이프 버퍼로 교체하고 약간의 조작을 통해 댕글링 mach 포트가 메모리의 어디에 있는지 알아냅니다.
그곳에 가짜 커널 태스크 포트를 만들어 정리합니다.
신뢰성: 익스플로잇은 작동합니다. 그것이 제 목표였습니다 :) 신뢰성은 약 30% 정도일 수 있습니다. 모든 것은 초기 오버플로 및 테스트 루프를 얼마나 빨리 수행할 수 있는지에 달려 있습니다. 다른 것이 kalloc.16에서 할당하거나 해제하면 프리리스트 항목이나 다른 것을 손상시킬 확률이 높아지며 패닉이 발생합니다.
익스플로잇을 더 신뢰성 있게 만들 수 있다고 확신합니다. 저는 이 버그가 익스플로잇 가능함을 입증하는 지점까지만 도달했습니다. 이를 시작점으로 삼아 신뢰성을 개선하는 방법을 시연하고 싶다면 블로그 게시물을 읽고 싶습니다! 아마도 kalloc.16 할당을 실제로 모니터링하고 실패 사례가 무엇이며 어떻게 방지할 수 있는지 이해하는 것이 포함될 것입니다.
성공률은 장치가 재부팅되고 잠시 유휴 상태일 때 가장 높은 것 같습니다.
정리: 익스플로잇이 작동하면 자체 정리되어 장치가 패닉되지 않아야 합니다. 가짜 커널 태스크 포트는 계속 유지됩니다.
kmem.h의 함수를 사용하여 커널 메모리를 읽고 쓰십시오. 이 프로세스가 종료된 후에도 커널 메모리 액세스를 유지하려면 tfp0에 대한 send-right를 유지하십시오.
테스트한 기기: iPod Touch 6G, iPhone 6S, iPhone SE, iPhone 7, iPhone 8 iOS 11부터 iOS 11.3.1까지에서 작동해야 합니다.