
Salida del registro del núcleo después de compilar y ejecutar example/open1_hook.c
Xnuspy es un módulo Pongoos que instala una nueva llamada del sistema, xnuspy_ctl , que le permite enganchar funciones del núcleo desde UserSpace. Admite iOS 13.x, iOS 14.x e iOS 15.X en checkra1n 0.12.2 y arriba. Los dispositivos 4K no son compatibles.
Este módulo neutraliza por completo a KTRR/KPP y permite crear memoria RWX dentro de EL1. No use esto en su conductor diario.
Requiere libusb : brew install libusb
Ejecute make en el directorio de nivel superior. Construirá el cargador y el módulo.
Agregue estos antes de make .
XNUSPY_DEBUG=1kprintf ).XNUSPY_SERIAL=1IOLog .XNUSPY_LEAKED_PAGE_LIMIT=n64 . Se puede encontrar más información en la depuración del pánico del núcleo.XNUSPY_TRAMP_PAGES=n XNUSPY_DEBUG y XNUSPY_SERIAL no dependen entre sí.
Después de haber construido todo, haga que Checkra1n arranca su dispositivo en un shell pongo: /Applications/checkra1n.app/Contents/MacOS/checkra1n -p
En el mismo directorio que construyó el cargador y el módulo, haga loader/loader module/xnuspy . Después de hacer eso, Xnuspy hará lo suyo y en unos segundos su dispositivo arrancará. loader esperará un par de segundos más después de emitir xnuspy-getkernelv en caso de que Seprom necesite ser explotado.
A veces, un par de mis teléfonos se atascaban al "arrancar" después de que se ejecuta KPF de Checkra1n. Todavía tengo que descubrir qué causa esto, pero si sucede, intente nuevamente. Además, si el dispositivo cuelga después de bootx , intente nuevamente. Finalmente, marcar el código xnuspy_ctl compilado como ejecutable en mi iPhone X que ejecuta iOS 13.3.1 es un poco irregular, pero tiene éxito el 100% del tiempo en mis otros teléfonos. Si entra en pánico con un Kernel Instruction, obtenga un rendimiento cuando ejecute su programa de gancho, intente nuevamente.
XNUSPY parchará una llamada del sistema enosys para apuntar a xnuspy_ctl_tramp . Este es un pequeño trampolín que marca el código xnuspy_ctl compilado como ejecutable y se ramifica a él. Puede encontrar la implementación de xnuspy_ctl en module/el1/xnuspy_ctl/xnuspy_ctl.c y ejemplos en el directorio example .
Interior include/xnuspy/ es xnuspy_ctl.h , un encabezado que define constantes para xnuspy_ctl . Está destinado a ser incluido en todos los programas que enganchan las funciones del núcleo.
Puede usar sysctlbyname para averiguar qué llamada del sistema fue parcheado:
size_t oldlen = sizeof(long);
long SYS_xnuspy_ctl = 0;
sysctlbyname("kern.xnuspy_ctl_callnum", &SYS_xnuspy_ctl, &oldlen, NULL, 0);
Esta llamada del sistema toma cuatro argumentos, flavor , arg1 , arg2 y arg3 . El sabor puede ser XNUSPY_CHECK_IF_PATCHED , XNUSPY_INSTALL_HOOK , XNUSPY_REGISTER_DEATH_CALLBACK , XNUSPY_CALL_HOOKME , XNUSPY_CACHE_READ , XNUSPY_KREAD , XNUSPY_KWRITE o XNUSPY_GET_CURRENT_THREAD . El significado de los siguientes tres argumentos depende del sabor.
XNUSPY_CHECK_IF_PATCHED Esto existe para que pueda verificar si hay xnuspy_ctl presente. Invocarlo con este sabor hará que regrese 999 . Se ignoran los valores de los otros argumentos.
XNUSPY_INSTALL_HOOK Diseñé este sabor para que coincida con la API de MSHookFunction . arg1 es la dirección insectada de la función del núcleo que desea enganchar. Si proporciona una dirección deslizante, lo más probable es que se asuste. arg2 es un puntero a su función de reemplazo compatible con ABI. arg3 es un puntero para que xnuspy_ctl copyout la dirección de un trampolín que representa la función del núcleo original. Esto puede ser NULL si no tiene la intención de llamar al original.
XNUSPY_REGISTER_DEATH_CALLBACKEste sabor le permite registrar una "devolución de llamada de muerte" opcional, una función Xnuspy llamará cuando salga el programa de gancho. Te da la oportunidad de limpiar todo lo que creaste de tus ganchos de kernel. Si creó algún hilo de núcleo, les diría que terminen en esta función.
Su devolución de llamada no se invoca asincrónicamente, por lo que si bloquea, está evitando que el hilo de recolección de basura de Xnuspy se ejecute.
arg1 es un puntero a su función de devolución de llamada. Se ignoran los valores de los otros argumentos.
XNUSPY_CALL_HOOKME hookme es un pequeño trozo de ensamblaje que Xnuspy exporta a través del caché Xnuspy para que se enganche. Invocar xnuspy_ctl con este sabor hará que hookme sea llamado, proporcionando una forma de obtener fácilmente la ejecución del código del núcleo sin tener que enganchar una función real del núcleo.
arg1 es un argumento que se pasará a hookme cuando se invoque. Esto puede ser NULL .
XNUSPY_CACHE_READ Este sabor le brinda una forma de leer desde el caché Xnuspy. Contiene muchas cosas útiles como kprintf , current_proc , kernel_thread_start , algunas funciones libc y la diapositiva del kernel para que no tenga que encontrarlas usted mismo. Para obtener una lista completa de ID de caché, consulte example/xnuspy_ctl.h .
arg1 es uno de los ID de caché definidos en xnuspy_ctl.h y arg2 es un puntero para xnuspy_ctl para copyout la dirección o el valor de lo que solicitó. Se ignoran los valores de los otros argumentos.
XNUSPY_KREADEste sabor le brinda una manera fácil de leer la memoria del núcleo del espacio de usuarios sin TFP0.
arg1 es una dirección virtual del núcleo, arg2 es la dirección de un búfer de espacio de usuario y arg3 es el tamaño de ese búfer del espacio de usuario. Los bytes arg3 se escribirán de arg1 a arg2 .
XNUSPY_KWRITEEste sabor le brinda una manera fácil de escribir en la memoria del núcleo desde el espacio de usuarios sin TFP0.
arg1 es una dirección virtual del núcleo, arg2 es la dirección de un búfer de espacio de usuario y arg3 es el tamaño de ese búfer del espacio de usuario. arg3 bytes se escribirá de arg2 a arg1 .
XNUSPY_GET_CURRENT_THREADEste sabor proporciona el espacio de usuarios la dirección del núcleo del hilo de llamada.
arg1 es un puntero para xnuspy_ctl para copyout el valor de retorno de current_thread . Se ignoran los valores de los otros argumentos.
Para todos los sabores, excepto XNUSPY_CHECK_IF_PATCHED , 0 se devuelve al éxito. Tras el error, -1 se devuelve y errno se establece. XNUSPY_CHECK_IF_PATCHED no devuelve ningún error. XNU's mach_to_bsd_errno se usa para convertir un kern_return_t a la errno apropiada.
XNUSPY_INSTALL_HOOK errno está configurado en ...
EEXIST si:arg1 .ENOMEM si:unified_kalloc devuelto NULL .ENOSPC si:xnuspy_tramp , una estructura de datos interna a XNUSPY. Esto no debería suceder a menos que estés enganchando cientos de funciones de núcleo al mismo tiempo . Si necesita más ganchos de funciones, consulte los límites.ENOTSUP si:ENOENT si:mh_for_addr no pudo determinar el encabezado Mach-O correspondiente a arg2 dentro del espacio de direcciones de la persona que llama.EFAULT si:EIO si:mach_make_memory_entry_64 no devolvió una entrada de memoria para la totalidad de los segmentos de __TEXT y __DATA de encabezado Mach-O determinados. errno también depende del valor de retorno de vm_map_wire_external , mach_vm_map_external , mach_make_memory_entry_64 , copyin , copyout , y si corresponde, la función de inicialización única.
Si este sabor devuelve un error, la función de núcleo de destino no se enganchó. Si aprobó un puntero no NULL para arg3 , puede o no haberse inicializado o no. No es seguro de usar si lo era.
XNUSPY_REGISTER_DEATH_CALLBACK errno está configurado en ...
ENOENT si:Si este sabor devuelve un error, su devolución de llamada de muerte no fue registrada.
XNUSPY_CALL_HOOKME errno está configurado en ...
ENOTSUP si:hookme está demasiado lejos de la memoria que contiene las estructuras xnuspy_tramp . Esto se determina dentro de Pongoos, y solo puede suceder si Xnuspy tuviera que retrasar el código no utilizado ya dentro del kernelcache. En este caso, llamar hookme casi seguramente causaría un pánico del núcleo, y tendrá que descubrir otra función del núcleo para engancharse. Si este sabor devuelve un error, no se llamó hookme .
XNUSPY_CACHE_READ errno está configurado en ...
EINVAL si:arg1 no representa nada en el caché.arg1 era IO_LOCK , pero el núcleo es iOS 14.4.2 o menos o iOS 15.x.arg1 fue IPC_OBJECT_LOCK , pero el kernel es iOS 15.x.arg1 fue IPC_PORT_RELEASE_SEND , pero el núcleo es iOS 14.5 o superior.arg1 fue IPC_PORT_RELEASE_SEND_AND_UNLOCK , pero el núcleo es iOS 14.4.2 o menos.arg1 fue KALLOC_CANBLOCK , pero el núcleo es iOS 14.x o superior.arg1 fue KALLOC_EXTERNAL , pero el kernel es iOS 13.x.arg1 fue KFREE_ADDR , pero el núcleo es iOS 14.x o superior.arg1 fue KFREE_EXT , pero el kernel es iOS 13.x.arg1 fue PROC_REF , pero el núcleo es iOS 14.8 o menos.arg1 fue PROC_REF_LOCKED , pero el kernel es iOS 15.x.arg1 fue PROC_RELE , pero el núcleo es iOS 14.8 o menos.arg1 fue PROC_RELE_LOCKED , pero el kernel es iOS 15.x.arg1 fue VM_MAP_UNWIRE , pero el kernel es iOS 15.x.arg1 fue VM_MAP_UNWIRE_NESTED , pero el kernel es iOS 14.8 o menos. errno también depende del valor de retorno de copyout y, si se aplica, el valor de retorno de la función de inicialización única.
Si este sabor devuelve un error, el puntero que pasó para arg2 no se inicializó.
XNUSPY_KREAD y XNUSPY_KWRITE errno está configurado en ...
EFAULT si:arg1 o arg2 . Si compiló con XNUSPY_DEBUG=1 , un mensaje al respecto se imprime en el registro del núcleo.Si este sabor devuelve un error, la memoria del kernel no fue leída/escrita.
XNUSPY_GET_CURRENT_THREAD Si copyout falla, errno se establece en su valor de retorno.
Mientras escribía funciones de reemplazo, era fácil olvidar que estaba escribiendo código de kernel. Aquí hay un par de cosas a tener en cuenta cuando escribes ganchos:
__TEXT de su programa . En pánico si, por ejemplo, accidentalmente llame printf en lugar de kprintf . Debe volver a implementar cualquier función libc que desee llamar si esa función aún no está disponible a través de XNUSPY_CACHE_READ . Sin embargo, puede crear consejos de funciones para otras funciones del núcleo y llamarlos.PAGE_SIZE se expande a vm_page_size , no una constante. Debe deshabilitar PAN (en A10+, que tampoco recomiendo hacer) antes de leer esta variable o en pánico.-fno-stack-protector y -D_FORTIFY_SOURCE=0 En algunos casos, el dispositivo tendrá que leer ___stack_chk_guard desactivando otro puntero de espacio de usuario, que entrará en pánico en A10+.Skimming https://developer.apple.com/library/archive/documentation/darwin/conceptual/kernelprogramming/style/style.html también se recomienda.
Los errores son inevitables al escribir código, por lo que eventualmente va a causar un pánico del núcleo. Un pánico no significa necesariamente que haya un error con Xnuspy, por lo que antes de abrir un problema, asegúrese de que aún se asuste cuando no haga nada más que llamar a la función original y devolver su valor (si es necesario). Si aún se asusta, es probable que sea un error Xnuspy (y abra un problema), pero si no, hay algo mal con su reemplazo.
Dado que Xnuspy en realidad no redirige la ejecución a las páginas El0, la depuración de un pánico no es tan sencillo. Abra module/el1/xnuspy_ctl/xnuspy_ctl.c , y justo antes de la única llamada a kwrite_instr en xnuspy_install_hook , agregue una llamada a IOSleep durante un par de segundos. Esto se hace para asegurarse de que haya suficiente tiempo antes de que el dispositivo se interponga para que los registros se propagen. Vuelva a compilar xnuspy con XNUSPY_DEBUG=1 make -B y cargue el módulo nuevamente. Después de cargar el módulo, si aún no lo ha hecho, compile klog de klog/ . Sube a tu dispositivo y haz stdbuf -o0 ./klog | grep shared_mapping_kva . Ejecute su programa de gancho nuevamente y observe una línea de klog que se vea así:
shared_mapping_kva: dist 0x7af4 uaddr 0x104797af4 umh 0x104790000 kmh 0xfffffff00c90c000
Si está instalando más de un gancho, habrá más de una ocurrencia. En ese caso, dist y uaddr variarán, pero umh y kmh no lo harán. kmh señala el comienzo del mapeo del núcleo del segmento __TEXT de su programa. Lanza tu programa de gancho a tu desapsilador favorito y vuelve a rebajarlo para que su encabezado Mach-O esté en la dirección de kmh . Para Ida Pro, eso es Edit -> Segments -> Rebase program... con Image base burbujeada. Después de que su dispositivo se enmienda y reinicie nuevamente, si hay direcciones que corresponden al mapeo del núcleo de su reemplazo en el registro de pánico, coincidirán con el desmontaje. Si no hay ninguno, entonces probablemente tenga algún tipo de corrupción de memoria sutil dentro de su reemplazo.
Xnuspy tampoco tiene forma de saber si un hilo del núcleo todavía se está ejecutando (o se ejecutará) en el mapeo del núcleo del segmento __TEXT de su programa después de que sus ganchos están desinstalados. Una de las cosas que Xnuspy hace para lidiar con esto es no desear esta asignación inmediatamente después de que su programa de gancho muera. En cambio, se agrega al final de una cola. Una vez que el hilo de recolección de basura de Xnuspy se da cuenta de que se ha excedido un límite fijo con respecto a cuántas páginas se mantienen en esa cola, comenzará a traer de la parte delantera y continuará hasta que ya no se supere ese límite. Por defecto, este límite es de 1 MB, o 64 páginas.
Si bien esto ayuda enormemente, cuanto más grandes sean los segmentos __TEXT y __DATA de su programa de gancho, menos probable Xnuspy gana esta carrera. Si está en pánico regularmente y tiene un programa de gancho algo grande, intente aumentar este límite agregando XNUSPY_LEAKED_PAGE_LIMIT=n antes de make . Esto establecerá este límite en n páginas en lugar de 64.
Xnuspy reserva una página de memoria del núcleo estático antes de las botas XNU para sus estructuras xnuspy_tramp , permitiéndole enganchar simultáneamente alrededor de 225 funciones del núcleo. Si desea más, puede agregar XNUSPY_TRAMP_PAGES=n antes de make . Esto le dirá a XNUSPY que reserve n páginas de memoria estática para estructuras xnuspy_tramp . Sin embargo, si Xnuspy tiene que recurrir al código no utilizado ya dentro del kernelcache, entonces esto se ignora. Cuando esto sucede se detalla en cómo funciona.
Por alguna razón, los registros de os_log_with_args no se muestran en la transmisión salida de la herramienta de línea de comando oslog . Los registros de kprintf tampoco lo llegan allí, pero se pueden ver con dmesg . Sin embargo, dmesg no es una feed en vivo, así que escribí klog , una herramienta que muestra registros de kprintf en tiempo real. Encuéntralo en klog/ . Recomiendo usar eso en lugar de enviar spam dmesg para sus mensajes kprintf .
Si se open: Resource busy después de ejecutar klog , ejecute este comando launchctl unload /System/Library/LaunchDaemons/com.apple.syslogd.plist e intente nuevamente.
Desafortunadamente, no podrá ver ningún NSLog si atm_diagnostic_config=0x20000000 está configurado en los bootargs de XNU. klog depende de que este argumento de arranque esté presente. Si desea NSLog vuelva, elimine ese argumento de arranque de pongo_send_command Inside loader.c .
Xnuspy lo administrará para ti. Una vez que sale un proceso, todos los ganchos de núcleo que fueron instalados por ese proceso no están instalados en un segundo más o menos.
La mayoría de los marcos de enganche de funciones tienen una longitud mínima que hace que una función determinada sea enganchable. Xnuspy tiene este límite solo si planea llamar a la función original y la primera instrucción de la función enganchada no es B . En este caso, la longitud mínima es de ocho bytes. De lo contrario, no hay longitud mínima.
Xnuspy usa X16 y X17 para sus trampolines, por lo que las funciones del núcleo que esperan que persistan en las llamadas de funciones no se pueden enganchar (no hay muchos que esperen esto). Si la función que desea enganchar comienza con BL y tiene la intención de llamar al original, solo puede hacerlo si ejecutar la función original no modifica X17 .
xnuspy_ctl realizará una inicialización única la primera vez que se llama después de un arranque nuevo. Esta es la única parte de Xnuspy que es compatible ya que no puedo inicializar estáticamente el bloqueo de lectura/escritura que uso. Después de la primera llamada de devoluciones, cualquier llamada futura es Garentreed para ser segura de hilo.
Esto se simplifica, pero captura bien la idea principal. Un gancho de funciones en Xnuspy es una estructura que reside en la memoria de núcleo ejecutable y escritas. En la mayoría de los casos, esta es la memoria devuelta por alloc_static dentro de Pongoos. Se puede reducir a esto:
struct {
uint64_t replacement;
uint32_t tramp[2];
uint32_t orig[10];
};
Donde replacement es la dirección virtual del núcleo (elaborada más tarde) de la función de reemplazo, tramp es un pequeño trampolín que redirige la ejecución para replacement , y orig es un trampolín más grande y más complicado que representa la función original.
Una de las primeras cosas que hace Xnuspy es determinar dónde reside el reemplazo de El0 dentro del espacio de direcciones de los procesos de llamadas. Esto se hace para que las funciones del núcleo se puedan enganchar desde bibliotecas dinámicas. Se guarda el encabezado Mach-O que corresponde a la dirección de ese reemplazo.
Después, un mapeo compartido de kernel de usuario de los segmentos __TEXT y __DATA de ese encabezado (así como cualquier segmento entre los que, si los hay) se crean. __TEXT se comparte para que pueda llamar a otras funciones desde sus ganchos. __DATA se comparte por lo que EL1 y EL0 ve cambios en las variables globales.
Dado que este mapeo es una copia uno a uno de __TEXT y __DATA , es fácil descubrir la dirección de la función de reemplazo del usuario. Dada la dirección del encabezado Mach u de los procesos de llamadas, la dirección del inicio del mapeo compartido k y la dirección de la función de reemplazo del usuario r , aplicamos la siguiente fórmula: replacement = k + (r - u)
Después de eso, replacement es la dirección virtual del núcleo de la función de reemplazo del usuario en el mapeo compartido y se escribe en la estructura de gancho de funciones. Xnuspy no redirige la ejecución a la dirección El0 de la función de reemplazo porque eso es extremadamente inseguro: no solo nos pone a merced del planificador, sino que no nos da control sobre el escenario donde un proceso con un gancho de núcleo muere mientras un hilo de núcleo aún se ejecuta en el reemplazo.
Finalmente, el mapeo compartido se marca como ejecutable y se ensambla una rama inmediata incondicional ( B ). Dirige la ejecución al comienzo de tramp , y es lo que reemplaza la primera instrucción de la función de núcleo ahora modificada. Desafortunadamente, esto nos limita de ramificación a estructuras de gancho a más de 128 MB de una función de núcleo dada. Xnuspy verifica este escenario antes de arrancar y recurre al código no utilizado que ya está en el kernelcache para que las estructuras del gancho reside en su lugar si descubre que esto podría suceder.
Hago todo lo posible para asegurarme de que los PatchFinders funcionen, por lo que si algo no funciona, abra un problema.