hacktricks/macos-hardening/macos-security-and-privilege-escalation/macos-proces-abuse/macos-ipc-inter-process-communication/macos-thread-injection-via-task-port.md

12 KiB

Inyección de hilos en macOS a través del puerto de tareas

Aprende hacking en AWS de cero a héroe con htARTE (HackTricks AWS Red Team Expert)!

Otras formas de apoyar a HackTricks:

Código

1. Secuestro de hilos

Inicialmente, se invoca la función task_threads() en el puerto de tareas para obtener una lista de hilos de la tarea remota. Se selecciona un hilo para el secuestro. Este enfoque se desvía de los métodos convencionales de inyección de código ya que la creación de un nuevo hilo remoto está prohibida debido a la nueva mitigación que bloquea thread_create_running().

Para controlar el hilo, se llama a thread_suspend(), deteniendo su ejecución.

Las únicas operaciones permitidas en el hilo remoto implican detenerlo y arrancarlo, recuperar y modificar sus valores de registro. Las llamadas a funciones remotas se inician configurando los registros x0 a x7 para los argumentos, configurando pc para apuntar a la función deseada y activando el hilo. Asegurar que el hilo no se caiga después del retorno requiere detectar el retorno.

Una estrategia implica registrar un manejador de excepciones para el hilo remoto usando thread_set_exception_ports(), estableciendo el registro lr en una dirección inválida antes de la llamada a la función. Esto desencadena una excepción después de la ejecución de la función, enviando un mensaje al puerto de excepción, lo que permite inspeccionar el estado del hilo para recuperar el valor de retorno. Alternativamente, como se adopta del exploit triple_fetch de Ian Beer, lr se configura para bucle infinito. Luego, se monitorean continuamente los registros del hilo hasta que pc apunte a esa instrucción.

2. Puertos Mach para comunicación

La fase subsiguiente implica establecer puertos Mach para facilitar la comunicación con el hilo remoto. Estos puertos son fundamentales para transferir derechos de envío y recepción arbitrarios entre tareas.

Para la comunicación bidireccional, se crean dos derechos de recepción Mach: uno en la tarea local y otro en la tarea remota. Posteriormente, se transfiere un derecho de envío para cada puerto a la tarea contraparte, permitiendo el intercambio de mensajes.

Centrándose en el puerto local, el derecho de recepción lo mantiene la tarea local. El puerto se crea con mach_port_allocate(). El desafío radica en transferir un derecho de envío a este puerto a la tarea remota.

Una estrategia implica aprovechar thread_set_special_port() para colocar un derecho de envío al puerto local en el THREAD_KERNEL_PORT del hilo remoto. Luego, se instruye al hilo remoto para que llame a mach_thread_self() para recuperar el derecho de envío.

Para el puerto remoto, el proceso es esencialmente inverso. Se dirige al hilo remoto para generar un puerto Mach a través de mach_reply_port() (ya que mach_port_allocate() no es adecuado debido a su mecanismo de retorno). Tras la creación del puerto, se invoca mach_port_insert_right() en el hilo remoto para establecer un derecho de envío. Este derecho se almacena luego en el kernel usando thread_set_special_port(). De vuelta en la tarea local, se utiliza thread_get_special_port() en el hilo remoto para adquirir un derecho de envío al puerto Mach recién asignado en la tarea remota.

La finalización de estos pasos resulta en el establecimiento de puertos Mach, sentando las bases para la comunicación bidireccional.

3. Primitivas básicas de lectura/escritura de memoria

En esta sección, el enfoque está en utilizar la primitiva de ejecución para establecer primitivas básicas de lectura y escritura de memoria. Estos pasos iniciales son cruciales para obtener más control sobre el proceso remoto, aunque las primitivas en esta etapa no servirán para muchos propósitos. Pronto, se mejorarán a versiones más avanzadas.

Lectura y escritura de memoria utilizando la primitiva de ejecución

El objetivo es realizar la lectura y escritura de memoria utilizando funciones específicas. Para leer memoria, se utilizan funciones con la siguiente estructura:

uint64_t read_func(uint64_t *address) {
return *address;
}

Y para escribir en la memoria, se utilizan funciones similares a esta estructura:

void write_func(uint64_t *address, uint64_t value) {
*address = value;
}

Estas funciones corresponden a las siguientes instrucciones de ensamblaje:

_read_func:
ldr x0, [x0]
ret
_write_func:
str x1, [x0]
ret

Identificación de Funciones Adecuadas

Un escaneo de bibliotecas comunes reveló candidatos apropiados para estas operaciones:

  1. Lectura de Memoria: La función property_getName() de la biblioteca de tiempo de ejecución de Objective-C se identifica como una función adecuada para la lectura de memoria. La función se describe a continuación:
const char *property_getName(objc_property_t prop) {
return prop->name;
}

Esta función actúa efectivamente como la read_func al devolver el primer campo de objc_property_t.

  1. Escritura de Memoria: Encontrar una función preconstruida para escribir en memoria es más desafiante. Sin embargo, la función _xpc_int64_set_value() de libxpc es un candidato adecuado con el siguiente desensamblado:
__xpc_int64_set_value:
str x1, [x0, #0x18]
ret

Para realizar una escritura de 64 bits en una dirección específica, la llamada remota se estructura como:

_xpc_int64_set_value(address - 0x18, value)

Con estas primitivas establecidas, se prepara el escenario para crear memoria compartida, marcando un progreso significativo en el control del proceso remoto.

4. Configuración de Memoria Compartida

El objetivo es establecer memoria compartida entre tareas locales y remotas, simplificando la transferencia de datos y facilitando la llamada de funciones con múltiples argumentos. El enfoque implica aprovechar libxpc y su tipo de objeto OS_xpc_shmem, que se construye sobre entradas de memoria Mach.

Resumen del Proceso:

  1. Asignación de Memoria:
  • Asignar la memoria para compartir usando mach_vm_allocate().
  • Utilizar xpc_shmem_create() para crear un objeto OS_xpc_shmem para la región de memoria asignada. Esta función gestionará la creación de la entrada de memoria Mach y almacenará el derecho de envío Mach en el desplazamiento 0x18 del objeto OS_xpc_shmem.
  1. Creando Memoria Compartida en el Proceso Remoto:
  • Asignar memoria para el objeto OS_xpc_shmem en el proceso remoto con una llamada remota a malloc().
  • Copiar el contenido del objeto OS_xpc_shmem local al proceso remoto. Sin embargo, esta copia inicial tendrá nombres de entradas de memoria Mach incorrectos en el desplazamiento 0x18.
  1. Corrigiendo la Entrada de Memoria Mach:
  • Utilizar el método thread_set_special_port() para insertar un derecho de envío para la entrada de memoria Mach en la tarea remota.
  • Corregir el campo de entrada de memoria Mach en el desplazamiento 0x18 sobrescribiéndolo con el nombre de la entrada de memoria remota.
  1. Finalizando la Configuración de Memoria Compartida:
  • Validar el objeto OS_xpc_shmem remoto.
  • Establecer el mapeo de memoria compartida con una llamada remota a xpc_shmem_remote().

Siguiendo estos pasos, la memoria compartida entre las tareas locales y remotas se configurará de manera eficiente, permitiendo transferencias de datos sencillas y la ejecución de funciones que requieren múltiples argumentos.

Fragmentos de Código Adicionales

Para la asignación de memoria y la creación de objetos de memoria compartida:

mach_vm_allocate();
xpc_shmem_create();

Para crear y corregir el objeto de memoria compartida en el proceso remoto:

malloc(); // for allocating memory remotely
thread_set_special_port(); // for inserting send right

Recuerde manejar correctamente los detalles de los puertos Mach y los nombres de entrada de memoria para asegurar que la configuración de memoria compartida funcione adecuadamente.

5. Logrando Control Total

Al establecer con éxito la memoria compartida y obtener capacidades de ejecución arbitraria, hemos ganado esencialmente control total sobre el proceso objetivo. Las funcionalidades clave que permiten este control son:

  1. Operaciones de Memoria Arbitrarias:
  • Realizar lecturas de memoria arbitrarias invocando memcpy() para copiar datos desde la región compartida.
  • Ejecutar escrituras de memoria arbitrarias utilizando memcpy() para transferir datos a la región compartida.
  1. Manejo de Llamadas a Funciones con Múltiples Argumentos:
  • Para funciones que requieren más de 8 argumentos, organizar los argumentos adicionales en la pila de acuerdo con la convención de llamadas.
  1. Transferencia de Puerto Mach:
  • Transferir puertos Mach entre tareas a través de mensajes Mach mediante puertos previamente establecidos.
  1. Transferencia de Descriptor de Archivo:
  • Transferir descriptores de archivo entre procesos utilizando fileports, una técnica destacada por Ian Beer en triple_fetch.

Este control comprensivo está encapsulado dentro de la biblioteca threadexec, proporcionando una implementación detallada y una API amigable para la interacción con el proceso víctima.

Consideraciones Importantes:

  • Asegúrese de usar correctamente memcpy() para operaciones de lectura/escritura de memoria para mantener la estabilidad del sistema y la integridad de los datos.
  • Al transferir puertos Mach o descriptores de archivo, siga los protocolos adecuados y maneje los recursos de manera responsable para prevenir fugas o accesos no intencionados.

Siguiendo estas pautas y utilizando la biblioteca threadexec, se puede gestionar e interactuar eficientemente con procesos a un nivel granular, logrando control total sobre el proceso objetivo.

Referencias

Aprende hacking en AWS de cero a héroe con htARTE (HackTricks AWS Red Team Expert)!

Otras formas de apoyar a HackTricks: