
Saída do log do kernel após compilar e executar example/open1_hook.c
O XNUSPY é um módulo Pongoos que instala uma nova chamada de sistema, xnuspy_ctl , que permite conectar as funções do kernel do UsersPace. Ele suporta iOS 13.x, iOS 14.x e iOS 15.x no checkra1n 0.12.2 e acima. Os dispositivos 4K não são suportados.
Este módulo neuza completamente o KTRR/KPP e possibilita a criação de memória RWX dentro do EL1. Não use isso no seu motorista diário.
Requer libusb : brew install libusb
make o Diretório de Nível superior. Ele criará o carregador e o módulo.
Adicione -os antes de make .
XNUSPY_DEBUG=1kprintf ).XNUSPY_SERIAL=1IOLog .XNUSPY_LEAKED_PAGE_LIMIT=n64 . Mais informações podem ser encontradas no pânico do kernel de depuração.XNUSPY_TRAMP_PAGES=n XNUSPY_DEBUG e XNUSPY_SERIAL não dependem um do outro.
Depois de construir tudo, faça o checkra1n seu dispositivo em um pongo shell: /Applications/checkra1n.app/Contents/MacOS/checkra1n -p
No mesmo diretório, você construiu o carregador e o módulo, faça loader/loader module/xnuspy . Depois de fazer isso, o XNUSPY fará o que fazer e em alguns segundos o seu dispositivo inicializará. loader esperará mais alguns segundos após a emissão xnuspy-getkernelv , caso a Seprom precise ser explorada.
Às vezes, alguns dos meus telefones ficavam presos em "inicializar" após as execuções do KPF de Checkra1n. Ainda tenho que descobrir o que causa isso, mas se acontecer, tente novamente. Além disso, se o dispositivo pendurar após bootx , tente novamente. Por fim, marcando o código xnuspy_ctl compilado como executável no meu iPhone X executando o iOS 13.3.1 é um pouco irregular, mas consegue 100% do tempo em meus outros telefones. Se você entrar em pânico com uma instrução do kernel, abortar quando você executar seu programa de gancho, tente novamente.
O XNUSPY fará corrigir um sistema enosys para apontar para xnuspy_ctl_tramp . Este é um pequeno trampolim que marca o código xnuspy_ctl compilado como executável e ramifica. Você pode encontrar a implementação do xnuspy_ctl no module/el1/xnuspy_ctl/xnuspy_ctl.c e exemplos no diretório example .
include/xnuspy/ é xnuspy_ctl.h , um cabeçalho que define constantes para xnuspy_ctl . Deve ser incluído em todos os programas que engancham as funções do kernel.
Você pode usar sysctlbyname para descobrir qual chamada do sistema foi corrigida:
size_t oldlen = sizeof(long);
long SYS_xnuspy_ctl = 0;
sysctlbyname("kern.xnuspy_ctl_callnum", &SYS_xnuspy_ctl, &oldlen, NULL, 0);
Esta chamada do sistema leva quatro argumentos, flavor , arg1 , arg2 e arg3 . O sabor pode ser XNUSPY_CHECK_IF_PATCHED , XNUSPY_INSTALL_HOOK , xnuspy_register_death_callback, XNUSPY_REGISTER_DEATH_CALLBACK , XNUSPY_CALL_HOOKME , XNUSPY_CACHE_READ , xnuspy_kwrite, ou XNUSPY_KREAD , xnuspy_k, XNUSPY_KWRITE , ou XNUSPY_GET_CURRENT_THREAD , O significado dos próximos três argumentos depende do sabor.
XNUSPY_CHECK_IF_PATCHED Isso existe para que você possa verificar se xnuspy_ctl está presente. Invocá -lo com esse sabor fará com que ele retorne 999 . Os valores dos outros argumentos são ignorados.
XNUSPY_INSTALL_HOOK Eu projetei esse sabor para corresponder à API da MSHookFunction . arg1 é o endereço não lado da função do kernel que você deseja conectar. Se você fornecer um endereço deslizante, provavelmente entrará em pânico. arg2 é um ponteiro para sua função de substituição compatível com ABI. arg3 é um ponteiro para xnuspy_ctl copyout o endereço de um trampolim que representa a função original do kernel. Isso pode ser NULL se você não pretende chamar o original.
XNUSPY_REGISTER_DEATH_CALLBACKEsse sabor permite que você registre um "retorno de chamada da morte" opcional, uma função Xnuspy ligará quando o programa do gancho sair. Dá a você a chance de limpar tudo o que você criou a partir de seus ganchos do kernel. Se você criou algum threads do kernel, diria -lhes para encerrar nesta função.
Seu retorno de chamada não é invocado de forma assíncrona; portanto, se você bloquear, estará impedindo a execução do tópico de coleta de lixo da XNUSPY.
arg1 é um ponteiro para sua função de retorno de chamada. Os valores dos outros argumentos são ignorados.
XNUSPY_CALL_HOOKME hookme é um pequeno stub de montagem que xnuspy exporta através do cache xnuspy para você conectar. Invocar xnuspy_ctl com esse sabor fará com que hookme seja chamado, fornecendo uma maneira de você obter facilmente a execução do código do kernel sem precisar conectar uma função real do kernel.
arg1 é um argumento que será passado para hookme quando for chamado. Isso pode ser NULL .
XNUSPY_CACHE_READ Esse sabor oferece uma maneira de ler do cache xnuspy. Ele contém muitas coisas úteis como kprintf , current_proc , kernel_thread_start , algumas funções da libc e o slide do kernel para que você não precise encontrá -las você mesmo. Para uma lista completa de IDs de cache, consulte example/xnuspy_ctl.h .
arg1 é um dos IDs de cache definidos em xnuspy_ctl.h e arg2 é um ponteiro para xnuspy_ctl para copyout o endereço ou o valor do que você solicitou. Os valores dos outros argumentos são ignorados.
XNUSPY_KREADEsse sabor oferece uma maneira fácil de ler a memória do kernel do UsersPace sem TFP0.
arg1 é um endereço virtual do kernel, arg2 é o endereço de um buffer de espaço de usuários, e arg3 é o tamanho desse buffer do UsuáriosPace. arg3 bytes será escrito de arg1 a arg2 .
XNUSPY_KWRITEEsse sabor oferece uma maneira fácil de escrever para a memória do kernel do UsuáriosPace sem TFP0.
arg1 é um endereço virtual do kernel, arg2 é o endereço de um buffer de espaço de usuários, e arg3 é o tamanho desse buffer do UsuáriosPace. arg3 bytes será escrito de arg2 a arg1 .
XNUSPY_GET_CURRENT_THREADEsse sabor fornece aos usuários o endereço do kernel do thread de chamada.
arg1 é um ponteiro para xnuspy_ctl para copyout o valor de retorno de current_thread . Os valores dos outros argumentos são ignorados.
Para todos os sabores, exceto XNUSPY_CHECK_IF_PATCHED , 0 é retornado no sucesso. Após o erro, -1 é retornado e errno está definido. XNUSPY_CHECK_IF_PATCHED não retorna nenhum erro. mach_to_bsd_errno do XNU é usado para converter um kern_return_t no errno apropriado.
XNUSPY_INSTALL_HOOK errno está definido como ...
EEXIST se:arg1 .ENOMEM se:unified_kalloc retornou NULL .ENOSPC se:xnuspy_tramp , uma estrutura de dados interna a xnuspy. Isso não deve acontecer, a menos que você esteja prejudicando centenas de funções do kernel ao mesmo tempo . Se você precisar de mais ganchos de função, consulte os limites.ENOTSUP se:ENOENT se:mh_for_addr não conseguiu determinar o cabeçalho Mach-O correspondente ao arg2 dentro do espaço de endereço do chamador.EFAULT se:EIO se:mach_make_memory_entry_64 não retornou uma entrada de memória para toda a totalidade dos segmentos __TEXT e __DATA do cabeçalho Mach-O determinados. errno também depende do valor de retorno de vm_map_wire_external , mach_vm_map_external , mach_make_memory_entry_64 , copyin , copyout e, se aplicável, a função de inicialização única.
Se esse sabor retornar um erro, a função do kernel de destino não foi viciada. Se você passou um ponteiro não NULL para arg3 , ele pode ou não ter sido inicializado. É inseguro de usar se fosse.
XNUSPY_REGISTER_DEATH_CALLBACK errno está definido como ...
ENOENT se:Se esse sabor retornar um erro, seu retorno de chamada de morte não foi registrado.
XNUSPY_CALL_HOOKME errno está definido como ...
ENOTSUP se:hookme está muito longe da memória que contém as estruturas xnuspy_tramp . Isso é determinado dentro de Pongoos e só pode acontecer se o Xnuspy tivesse que fazer fallback para o código não utilizado já dentro do Kernelcache. Nesse caso, o chamado hookme quase certamente causaria um pânico no kernel, e você terá que descobrir outra função do kernel para conectar. Se esse sabor retornar um erro, hookme não foi chamado.
XNUSPY_CACHE_READ errno está definido como ...
EINVAL se:arg1 não representa nada no cache.arg1 era IO_LOCK , mas o kernel é o iOS 14.4.2 ou abaixo ou iOS 15.x.arg1 foi IPC_OBJECT_LOCK , mas o kernel é iOS 15.x.arg1 foi IPC_PORT_RELEASE_SEND , mas o kernel é o iOS 14.5 ou acima.arg1 foi IPC_PORT_RELEASE_SEND_AND_UNLOCK , mas o kernel é o iOS 14.4.2 ou abaixo.arg1 era KALLOC_CANBLOCK , mas o kernel é o iOS 14.x ou acima.arg1 era KALLOC_EXTERNAL , mas o kernel é o iOS 13.x.arg1 era KFREE_ADDR , mas o kernel é o iOS 14.x ou acima.arg1 era KFREE_EXT , mas o kernel é o iOS 13.x.arg1 foi PROC_REF , mas o kernel é o iOS 14.8 ou abaixo.arg1 foi PROC_REF_LOCKED , mas o kernel é iOS 15.x.arg1 foi PROC_RELE , mas o kernel é o iOS 14.8 ou abaixo.arg1 foi PROC_RELE_LOCKED , mas o kernel é iOS 15.x.arg1 era VM_MAP_UNWIRE , mas o kernel é iOS 15.x.arg1 foi VM_MAP_UNWIRE_NESTED , mas o kernel é o iOS 14.8 ou abaixo. errno também depende do valor de retorno do copyout e, se aplicável, o valor de retorno da função de inicialização única.
Se esse sabor retornar um erro, o ponteiro que você passou para arg2 não foi inicializado.
XNUSPY_KREAD e XNUSPY_KWRITE errno está definido como ...
EFAULT se:arg1 ou arg2 . Se você compilou com XNUSPY_DEBUG=1 , uma mensagem é impressa no log do kernel.Se esse sabor retornar um erro, a memória do kernel não foi lida/escrita.
XNUSPY_GET_CURRENT_THREAD Se copyout falhar, errno será definido como seu valor de retorno.
Ao escrever funções de substituição, era fácil esquecer que eu estava escrevendo o código do kernel. Aqui estão algumas coisas a serem lembradas quando você está escrevendo ganchos:
__TEXT do seu programa . Você entrará em pânico se, por exemplo, você chama acidentalmente printf em vez de kprintf . Você precisa reimplementar qualquer função LIBC que você deseja ligar se essa função ainda não estiver disponível via XNUSPY_CACHE_READ . Você pode criar ponteiros de função para outras funções do kernel e chamá -las.PAGE_SIZE se expande para vm_page_size , não uma constante. Você precisa desativar o PAN (no A10+, o que eu também não recomendo fazer) antes de ler esta variável ou você entrará em pânico.-fno-stack-protector e -D_FORTIFY_SOURCE=0 em alguns casos, o dispositivo terá que ler ___stack_chk_guard , desperendendo outro ponteiro do espaço do usuário, que entrará em pânico no A10+.Skimming https://developer.apple.com/library/archive/documentation/darwin/conceptual/kernelprogramming/style/style.html também é recomendado.
Os insetos são inevitáveis ao escrever código, então, eventualmente, você causará um pânico no kernel. Um pânico não significa necessariamente que há um bug com o XNUSPY; portanto, antes de abrir um problema, verifique se você ainda entra em pânico quando não faz nada além de chamar a função original e retornar seu valor (se necessário). Se você ainda entrar em pânico, é provável que seja um bug xnuspy (e abra um problema), mas se não, há algo errado com sua substituição.
Como o XNUSPY não redireciona a execução para as páginas do EL0, a depuração de um pânico não é tão direta. Abra module/el1/xnuspy_ctl/xnuspy_ctl.c e logo antes da única chamada para kwrite_instr em xnuspy_install_hook , adicione uma chamada ao IOSleep por alguns segundos. Isso é feito para garantir que haja tempo suficiente antes que o dispositivo entre em pânico para os logs se propagam. Recompile xnuspy com XNUSPY_DEBUG=1 make -B e carregue o módulo novamente. Depois de carregar o módulo, se você ainda não o fez, compile klog do klog/ . Carregue -o para o seu dispositivo e faça stdbuf -o0 ./klog | grep shared_mapping_kva . Execute o programa do seu gancho novamente e observe uma linha do klog que se parece com o seguinte:
shared_mapping_kva: dist 0x7af4 uaddr 0x104797af4 umh 0x104790000 kmh 0xfffffff00c90c000
Se você estiver instalando mais de um gancho, haverá mais de uma ocorrência. Nesse caso, dist e uaddr variarão, mas umh e kmh não. kmh aponta para o início do mapeamento do kernel do segmento __TEXT do seu programa. Jogue seu programa de gancho em seu desmontador favorito e rebate-o para que seu cabeçalho Mach-O esteja no endereço da kmh . Para o IDA Pro, isso é Edit -> Segments -> Rebase program... com Image base borbulhada. Depois que o seu dispositivo entra em pânico e reinicia novamente, se houver endereços que correspondam ao mapeamento do seu substituto no registro de pânico, eles combinarão com a desmontagem. Se não houver, você provavelmente terá algum tipo de corrupção sutil de memória dentro da sua substituição.
O XNUSPY também não tem como saber se um tópico do kernel ainda está executando (ou será executado) no mapeamento do kernel do segmento __TEXT do seu programa após a desinstalação dos ganchos. Uma das coisas que Xnuspy faz para lidar com isso é não negociar esse mapeamento imediatamente após a morte do programa de gancho. Em vez disso, é adicionado ao final de uma fila. Depois que o encadeamento de coleta de lixo da XNUSPY avisa um limite definido, excedendo quantas páginas de mapeamentos são mantidas nessa fila, ele começará a se negociar da frente da fila e continuará até que esse limite não seja mais excedido. Por padrão, esse limite é de 1 MB ou 64 páginas.
Embora isso ajude enormemente, quanto maior os segmentos __TEXT e __DATA do seu programa de gancho se tornam, menor a probabilidade de Xnuspy vencer esta corrida. Se você estiver em pânico regularmente e possui um programa de gancho um pouco grande, tente aumentar esse limite adicionando XNUSPY_LEAKED_PAGE_LIMIT=n antes make . Isso definirá esse limite para n páginas em vez de 64.
O XNUSPY se reserva uma página de memória estática do kernel antes das botas XNU para suas estruturas xnuspy_tramp , permitindo que você conecte simultaneamente em torno de 225 funções do kernel. Se você quiser mais, você pode adicionar XNUSPY_TRAMP_PAGES=n antes make . Isso dirá ao XNUSPY para reservar n páginas de memória estática para estruturas xnuspy_tramp . No entanto, se o Xnuspy precisar voltar ao código não utilizado já dentro do kernelcache, isso será ignorado. Quando isso acontece, é detalhado em como funciona.
Por algum motivo, os logs de os_log_with_args não aparecem no fluxo em saída do oslog da ferramenta de linha de comando. Os logs do kprintf também não chegam lá, mas podem ser vistos com dmesg . No entanto, dmesg não é um feed ao vivo, então escrevi klog , uma ferramenta que mostra kprintf faz login em tempo real. Encontre -o em klog/ . Eu recomendo fortemente usá -lo em vez de enviar spam dmesg para suas mensagens kprintf .
Se você se open: Resource busy após a execução klog , execute este comando launchctl unload /System/Library/LaunchDaemons/com.apple.syslogd.plist e tente novamente.
Infelizmente, você não poderá ver nenhum NSLog se atm_diagnostic_config=0x20000000 estiver definido nos bootargs da XNU. klog depende desse argumento de inicialização estar presente. Se você deseja NSLog de volta, remova esse argumento de inicialização de pongo_send_command Inside loader.c .
Xnuspy gerenciará isso para você. Depois que um processo sai, todos os ganchos do kernel que foram instalados por esse processo são desinstalados em um segundo ou mais.
A maioria das estruturas de enforcamento de funções possui um comprimento mínimo que torna uma determinada função gancho. Xnuspy tem esse limite apenas se você planeja chamar a função original e a primeira instrução da função conectada não for B . Nesse caso, o comprimento mínimo é de oito bytes. Caso contrário, não há comprimento mínimo.
O XNUSPY usa X16 e X17 para seus trampolins, portanto, as funções do kernel que esperam que elas persistem entre chamadas de função não possam ser viciadas (não há muitas que esperam isso). Se a função que você deseja conectar começar com BL , e você pretende chamar o original, você só poderá fazê -lo se a execução da função original não modificar X17 .
xnuspy_ctl executará a inicialização única na primeira vez em que é chamado após uma inicialização nova. Esta é a única parte do Xnuspy, que é raciável, pois não posso inicializar estaticamente o bloqueio de leitura/gravação que uso. Após o retorno da primeira chamada, todas as chamadas futuras são protegidas para o thread.
Isso é simplificado, mas captura bem a idéia principal. Um gancho de função no XNUSPY é uma estrutura que reside na memória de kernel escritos e executável. Na maioria dos casos, isso é a memória retornada por alloc_static dentro de pongoos. Pode ser resumido para isso:
struct {
uint64_t replacement;
uint32_t tramp[2];
uint32_t orig[10];
};
Onde replacement é o endereço virtual do kernel (elaborado posteriormente) da função de reposição, tramp é um pequeno trampolim que redireciona a execução da replacement e orig é um trampolim maior e mais complicado que representa a função original.
Uma das primeiras coisas que Xnuspy faz é determinar onde a substituição do EL0 reside dentro do espaço de endereço dos processos de chamada. Isso é feito para que as funções do kernel possam ser conectadas a partir de bibliotecas dinâmicas. O cabeçalho Mach-O que corresponde ao endereço dessa substituição é salvo.
Depois, é criado um mapeamento de kernel do usuário compartilhado dos segmentos __TEXT e __DATA do cabeçalho (bem como qualquer segmento entre eles, se houver), é criado. __TEXT é compartilhado para que você possa chamar outras funções de seus ganchos. __DATA é compartilhado para que as mudanças nas variáveis globais sejam vistas por EL1 e EL0.
Como esse mapeamento é uma cópia individual de __TEXT e __DATA , é fácil descobrir o endereço da função de substituição do usuário. Dado o endereço do cabeçalho Mach -O dos Processos de Chamada u , o endereço do início do mapeamento compartilhado k e o endereço da função de substituição do usuário r , aplicamos a seguinte fórmula: replacement = k + (r - u)
Depois disso, replacement é o endereço virtual do kernel da função de substituição do usuário no mapeamento compartilhado e é gravado na estrutura do gancho de função. O XNUSPY não redireciona a execução para o endereço EL0 da função de reposição, porque isso é extremamente inseguro: isso não apenas nos coloca à mercê do agendador, mas também não nos dá controle sobre o cenário em que um processo com um gancho de kernel morre enquanto um fio de kernel ainda está executando a substituição.
Finalmente, o mapeamento compartilhado é marcado como executável e um ramo imediato incondicional ( B ) é montado. Ele direciona a execução para o início do tramp e é o que substitui a primeira instrução da função de kernel agora ganho. Infelizmente, isso nos limita da ramificação às estruturas do gancho a mais de 128 MB de uma determinada função do kernel. O Xnuspy verifica esse cenário antes de inicializar e volta ao código não utilizado já no Kernelcache para que as estruturas do gancho residam, em vez disso, se descobrir que isso pode acontecer.
Faço o meu melhor para garantir que os patchfinders funcionem; portanto, se algo não estiver funcionando, abra um problema.