
CVE-2018-4241: overflow dell'heap del kernel XNU a causa di un cattivo controllo dei limiti in MPTCP per iOS 11 - 11.3.1rilasciato da Ian Beer
@i41nbeer
mptcp_usr_connectx è il gestore per la syscall connectx per la famiglia di socket AP_MULTIPATH.
La logica di questa funzione non riesce a gestire correttamente gli indirizzi sockaddr di origine e destinazione che non sono AF_INET o AF_INET6:
// 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);
}
Guardando nella struttura che si overflowa all'interno, si nota che si possono colpire entrambi i campi qui:
if (mpte->mpte_itfinfo_size > MPTE_ITFINFO_SIZE) _FREE(mpte->mpte_itfinfo, M_TEMP);
mpte_itfinfo_size è appena prima di mpte_itfinfo.
Quando la struttura viene inizializzata, il puntatore mpte_itfinfo punta a un piccolo array inline. Se vengono aggiunti più subflow di quanti ne possano contenere, vengono invece inseriti in un buffer heap, e mpte_itfinfo punterà a quello.
Se si avesse un altro bug (ad esempio il bug di divulgazione dell'heap del kernel da async_wake) si potrebbe sovrascrivere il campo mpte_itfinfo con qualsiasi oggetto di zona valido e verrebbe liberato (in effetti, si potrebbe anche sovrascrivere con un offset all'interno di quell'oggetto per ancora più divertimento!)
Tuttavia, non abbiamo quello.
Invece un altro approccio è sovrascrivere parzialmente il puntatore. Se lo sovrascriviamo parzialmente con byte NULL, possiamo puntarlo a un valore allineato a 256 byte, 65k, 16MB o 4GB.
In questo exploit scelgo una sovrascrittura di 3 byte NULL, che causerà un kfree dell'indirizzo mpte_itfinfo arrotondato per difetto al successivo limite di 16MB.
Il flusso di sfruttamento è il seguente:
Allocare alternativamente 16MB di ipc_kmsgs seguiti da un mucchio di socket mptcp. L'obiettivo è ottenere un'allocazione kalloc.2048 a quel limite di 16MB.
Usare il bug per liberare uno degli ipc_kmsgs, spostando quella pagina nella lista intermedia e mettendo l'allocazione allineata a 16MB su una freelist di pagine intermedie kalloc.2048.
Allocare un mucchio di pipe piene di 2047 byte; i buffer di supporto per queste pipe proverranno da kalloc.2048, si spera incluso il nostro indirizzo allineato a 16MB.
Attivare il bug una seconda volta, liberando lo stesso indirizzo e questa volta allocare un mucchio di buffer ipc_kmsg preallocati da kalloc.2048.
Ora si spera di avere un ipc_kmsg (a cui possiamo far inviare messaggi e poi riceverli) e un buffer di pipe (che possiamo leggere e scrivere) sovrapposti tra loro.
Uso il trucco del thread exception port da extra_recipe per far inviare messaggi al buffer ipc_kmsg preallocato. Ogni volta controlliamo ciascuna delle pipe per vedere se qualcuna contiene il messaggio. Quando troviamo la coppia (ipc_kmsg, pipe) giusta, possiamo riscrivere il messaggio per inviarci una porta fittizia che risiede all'interno del buffer della pipe. Strutturo quella porta fittizia come quella di async_wake (che ho basato su yalu 10.2 di @qwertyoruiopz e @marcograss) per darmi una primitiva di lettura precoce del kernel.
Usando la primitiva di lettura del kernel trovo il task del kernel e creo una porta fittizia che permette una lettura/scrittura più facile della memoria del kernel tramite mach_vm_read/mach_vm_write.
Avvertenza: Per connettere i socket mptcp è necessaria l'entitlement com.apple.developer.networking.multipath che richiede un certificato sviluppatore Apple, che chiunque può acquistare da Apple.
Affidabilità: Questo è uno strumento di ricerca sulla sicurezza ed è molto lontano dall'essere perfetto. Tuttavia, dovrebbe funzionare la maggior parte delle volte, e quando funziona dovrebbe fare un buon lavoro di pulizia in modo che non vada in panico in seguito.
Per migliorare la probabilità che funzioni:
Dispositivi supportati: Dovrebbe funzionare su iOS 11.0 - 11.3.1 inclusi. Ho testato su: iPod Touch 6g, iPhone 6s, iPhone SE, iPhone 7, iPhone 8
API: #include "sploit.h" e chiamare go() per eseguire l'exploit. Se ha funzionato, è possibile utilizzare le funzioni in kmem.h per leggere e scrivere la memoria del kernel
Note: Molte persone hanno fatto un bindiff pubblico di questo bug dalla patch (o il loro 0day è stato patchato ;) leggete i loro lavori per maggiori dettagli: @elvanderb ha tenuto un lightning talk sul bug al rump.beer a Parigi il 31 maggio: https://www.rump.beer/2018/slides/ios_48h.pdf @jaakerblom ha pubblicato un exploit funzionante su github il 1 giugno: https://github.com/potmdehex/multipath_kfree La tecnica di John è simile alla mia ma lui fa un overflow di due byte invece di tre, e sostituisce con oggetti diversi. roba buona!