
CVE-2018-4241: iOS 11 - 11.3.1의 MPTCP에서 잘못된 경계 검사로 인한 XNU 커널 힙 오버플로우 (Ian Beer 공개)
@i41nbeer
mptcp_usr_connectx는 AP_MULTIPATH 소켓 패밀리의 connectx 시스템 콜을 위한 핸들러입니다.
이 함수의 로직은 AF_INET 또는 AF_INET6가 아닌 소스 및 대상 sockaddr을 올바르게 처리하지 못합니다.
// verify sa_len for AF_INET:
if (dst->sa_family == AF_INET &&
dst->sa_len != sizeof(mpte->__mpte_dst_v4)) {
mptcplog((LOG_ERR, "%s IPv4 dst len %u\n", __func__, dst->sa_len), MPTCP_SOCKET_DBG, MPTCP_LOGLVL_ERR);
error = EINVAL;
goto out;
}
// verify sa_len for AF_INET6:
if (dst->sa_family == AF_INET6 &&
dst->sa_len != sizeof(mpte->__mpte_dst_v6)) {
mptcplog((LOG_ERR, "%s IPv6 dst len %u\n", __func__, dst->sa_len), MPTCP_SOCKET_DBG, MPTCP_LOGLVL_ERR);
error = EINVAL;
goto out;
}
// code doesn't bail if sa_family was neither AF_INET nor AF_INET6
if (!(mpte->mpte_flags & MPTE_SVCTYPE_CHECKED)) {
if (mptcp_entitlement_check(mp_so) < 0) {
error = EPERM;
goto out;
}
mpte->mpte_flags |= MPTE_SVCTYPE_CHECKED;
}
// memcpy with sa_len up to 255:
if ((mp_so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING)) == 0) {
memcpy(&mpte->mpte_dst, dst, dst->sa_len);
}
오버플로우하는 구조체를 살펴보면 여기서 두 필드를 모두 건드릴 수 있음을 알 수 있습니다.
if (mpte->mpte_itfinfo_size > MPTE_ITFINFO_SIZE) _FREE(mpte->mpte_itfinfo, M_TEMP);
mpte_itfinfo_size는 mpte_itfinfo 바로 앞에 있습니다.
구조체가 초기화될 때 mpte_itfinfo 포인터는 작은 인라인 배열을 가리킵니다. 그곳에 맞지 않는 더 많은 서브플로우가 추가되면 대신 힙 버퍼에 배치되고 mpte_itfinfo는 해당 버퍼를 가리키게 됩니다.
다른 버그(예: async_wake의 커널 힙 공개 버그)가 있다면 mpte_itfinfo 필드를 유효한 존 객체로 덮어쓸 수 있으며, 그러면 해제됩니다(사실, 더 재미를 위해 그 객체 내의 오프셋으로 덮어쓸 수도 있습니다!).
하지만 우리는 그런 것이 없습니다.
대신 다른 접근 방식은 포인터를 부분적으로 덮어쓰는 것입니다. NULL 바이트로 부분적으로 덮어쓰면 256바이트, 65k, 16MB 또는 4GB 정렬된 값을 가리키게 할 수 있습니다.
이 exploit에서는 3바이트 NULL 덮어쓰기를 선택했으며, 이로 인해 mpte_itfinfo 주소가 다음 16MB 경계로 내림된 값이 kfree됩니다.
exploit 흐름은 다음과 같습니다:
16MB의 ipc_kmsg를 번갈아 할당한 다음 여러 mptcp 소켓을 할당합니다. 목표는 해당 16MB 경계에서 kalloc.2048 할당을 얻는 것입니다.
버그를 사용하여 ipc_kmsg 중 하나를 해제하고, 해당 페이지를 중간 목록으로 이동시킨 후 16MB 정렬된 할당을 kalloc.2048 중간 페이지 프리리스트에 넣습니다.
가득 찬 2047바이트 파이프를 여러 개 할당합니다; 이 파이프들의 백킹 버퍼는 kalloc.2048에서 가져오며, 우리의 16MB 정렬 주소를 포함하기를 바랍니다.
버그를 두 번째로 트리거하여 동일한 주소를 해제하고, 이번에는 kalloc.2048에서 미리 할당된 ipc_kmsg 버퍼를 여러 개 할당합니다.
이제 우리는 메시지를 보내고 받을 수 있는 ipc_kmsg와 읽고 쓸 수 있는 파이프 버퍼가 서로 겹쳐지기를 바랍니다.
extra_recipe의 스레드 예외 포트 트릭을 사용하여 미리 할당된 ipc_kmsg 버퍼로 메시지를 보냅니다. 각 파이프를 확인하여 메시지가 포함된 파이프를 찾습니다. 올바른 (ipc_kmsg, 파이프) 쌍을 찾으면 메시지를 다시 작성하여 파이프 버퍼 내에 있는 가짜 포트를 자신에게 보냅니다. @qwertyoruiopz와 @marcograss의 yalu 10.2를 기반으로 한 async_wake의 포트와 유사하게 가짜 포트를 구성하여 초기 커널 읽기 프리미티브를 얻습니다.
커널 읽기 프리미티브를 사용하여 커널 태스크를 찾고, mach_vm_read/mach_vm_write를 통해 더 쉬운 커널 메모리 읽기/쓰기를 가능하게 하는 가짜 포트를 만듭니다.
주의: mptcp 소켓을 연결하려면 com.apple.developer.networking.multipath 권한이 필요하며, 이는 Apple 개발자 인증서가 필요하며, 누구나 Apple에서 구매할 수 있습니다.
신뢰성: 이것은 보안 연구 도구이며 완벽과는 거리가 멉니다. 그러나 대부분의 경우 작동해야 하며, 작동할 때는 나중에 패닉이 발생하지 않도록 정리를 잘 해야 합니다.
작동 확률을 높이려면:
지원 기기: iOS 11.0부터 11.3.1까지 작동해야 합니다. 다음에서 테스트했습니다: iPod Touch 6g, iPhone 6s, iPhone SE, iPhone 7, iPhone 8
API: #include "sploit.h"를 포함하고 go()를 호출하여 exploit을 실행합니다. 작동했다면 kmem.h의 함수를 사용하여 커널 메모리를 읽고 쓸 수 있습니다.
참고: 여러 사람이 공개적으로 패치에서 이 버그를 bindiff했습니다(또는 그들의 0day가 패치되었습니다 ;) 자세한 내용은 그들의 자료를 읽어보세요: @elvanderb가 5월 31일 파리 rump.beer에서 이 버그에 대한 라이트닝 토크를 했습니다: https://www.rump.beer/2018/slides/ios_48h.pdf @jaakerblom이 6월 1일 github에 작동하는 exploit을 게시했습니다: https://github.com/potmdehex/multipath_kfree 존의 기법은 내 것과 유사하지만 3바이트 대신 2바이트 오버플로우를 사용하고 다른 객체로 대체합니다. 좋은 내용이에요!