macOS IPC - Inter Process Communication
Last updated
Last updated
Aprenda e pratique Hacking AWS:Treinamento HackTricks AWS Red Team Expert (ARTE) Aprenda e pratique Hacking GCP: Treinamento HackTricks GCP Red Team Expert (GRTE)
O Mach utiliza tarefas como a unidade mais pequena para compartilhar recursos, e cada tarefa pode conter múltiplas threads. Essas tarefas e threads são mapeadas em um para um com processos e threads POSIX.
A comunicação entre tarefas ocorre via Comunicação entre Processos Mach (IPC), utilizando canais de comunicação unidirecional. As mensagens são transferidas entre portas, que funcionam como filas de mensagens gerenciadas pelo kernel.
Uma porta é o elemento básico do IPC do Mach. Ela pode ser usada para enviar mensagens e recebê-las.
Cada processo possui uma tabela IPC, onde é possível encontrar as portas mach do processo. O nome de uma porta mach é na verdade um número (um ponteiro para o objeto do kernel).
Um processo também pode enviar um nome de porta com alguns direitos para uma tarefa diferente e o kernel fará com que essa entrada na tabela IPC da outra tarefa apareça.
Os direitos de porta, que definem quais operações uma tarefa pode realizar, são fundamentais para essa comunicação. Os possíveis direitos de porta são (definições daqui):
Direito de Receber, que permite receber mensagens enviadas para a porta. As portas Mach são filas MPSC (múltiplos produtores, um consumidor), o que significa que pode haver apenas um direito de receber para cada porta em todo o sistema (ao contrário de pipes, onde vários processos podem todos ter descritores de arquivo para a extremidade de leitura de um pipe).
Uma tarefa com o Direito de Receber pode receber mensagens e criar Direitos de Envio, permitindo enviar mensagens. Originalmente, apenas a própria tarefa tem o Direito de Receber sobre sua porta.
Se o proprietário do Direito de Receber morre ou o encerra, o direito de envio se torna inútil (nome morto).
Direito de Envio, que permite enviar mensagens para a porta.
O Direito de Envio pode ser clonado para que uma tarefa que possui um Direito de Envio possa clonar o direito e concedê-lo a uma terceira tarefa.
Note que os direitos de porta também podem ser passados por meio de mensagens Mac.
Direito de Envio-único, que permite enviar uma mensagem para a porta e depois desaparece.
Esse direito não pode ser clonado, mas pode ser movido.
Direito de conjunto de portas, que denota um conjunto de portas em vez de uma única porta. Desenfileirar uma mensagem de um conjunto de portas desenfileira uma mensagem de uma das portas que ele contém. Os conjuntos de portas podem ser usados para escutar várias portas simultaneamente, muito parecido com select
/poll
/epoll
/kqueue
no Unix.
Nome morto, que não é um direito de porta real, mas apenas um espaço reservado. Quando uma porta é destruída, todos os direitos de porta existentes para a porta se tornam nomes mortos.
Tarefas podem transferir DIREITOS DE ENVIO para outros, permitindo-lhes enviar mensagens de volta. DIREITOS DE ENVIO também podem ser clonados, para que uma tarefa possa duplicar e dar o direito a uma terceira tarefa. Isso, combinado com um processo intermediário conhecido como o servidor de inicialização, permite uma comunicação eficaz entre tarefas.
Portas de arquivo permitem encapsular descritores de arquivo em portas Mac (usando direitos de porta Mach). É possível criar um fileport
a partir de um FD dado usando fileport_makeport
e criar um FD a partir de um fileport usando fileport_makefd
.
Como mencionado anteriormente, é possível enviar direitos usando mensagens Mach, no entanto, você não pode enviar um direito sem já ter um direito para enviar uma mensagem Mach. Então, como é estabelecida a primeira comunicação?
Para isso, o servidor de inicialização (launchd no Mac) está envolvido, pois qualquer pessoa pode obter um DIREITO DE ENVIO para o servidor de inicialização, é possível pedir a ele um direito para enviar uma mensagem para outro processo:
A Tarefa A cria uma nova porta, obtendo o direito de RECEBER sobre ela.
A Tarefa A, sendo a detentora do direito de RECEBER, gera um DIREITO DE ENVIO para a porta.
A Tarefa A estabelece uma conexão com o servidor de inicialização, e envia a ele o DIREITO DE ENVIO para a porta que gerou no início.
Lembre-se de que qualquer pessoa pode obter um DIREITO DE ENVIO para o servidor de inicialização.
A Tarefa A envia uma mensagem bootstrap_register
para o servidor de inicialização para associar a porta fornecida a um nome como com.apple.taska
.
A Tarefa B interage com o servidor de inicialização para executar uma busca de inicialização para o nome do serviço (bootstrap_lookup
). Para que o servidor de inicialização possa responder, a tarefa B enviará um DIREITO DE ENVIO para uma porta que criou anteriormente dentro da mensagem de busca. Se a busca for bem-sucedida, o servidor duplica o DIREITO DE ENVIO recebido da Tarefa A e transmite para a Tarefa B.
Lembre-se de que qualquer pessoa pode obter um DIREITO DE ENVIO para o servidor de inicialização.
Com este DIREITO DE ENVIO, a Tarefa B é capaz de enviar uma mensagem para a Tarefa A.
Para uma comunicação bidirecional, geralmente a tarefa B gera uma nova porta com um direito de RECEBER e um DIREITO DE ENVIO, e dá o DIREITO DE ENVIO para a Tarefa A para que ela possa enviar mensagens para a TAREFA B (comunicação bidirecional).
O servidor de inicialização não pode autenticar o nome do serviço reivindicado por uma tarefa. Isso significa que uma tarefa poderia potencialmente falsificar qualquer tarefa do sistema, como reivindicar falsamente um nome de serviço de autorização e então aprovar cada solicitação.
Em seguida, a Apple armazena os nomes dos serviços fornecidos pelo sistema em arquivos de configuração seguros, localizados em diretórios protegidos pelo SIP: /System/Library/LaunchDaemons
e /System/Library/LaunchAgents
. Ao lado de cada nome de serviço, o binário associado também é armazenado. O servidor de inicialização, criará e manterá um direito de RECEBER para cada um desses nomes de serviço.
Para esses serviços predefinidos, o processo de busca difere ligeiramente. Quando um nome de serviço está sendo procurado, o launchd inicia o serviço dinamicamente. O novo fluxo de trabalho é o seguinte:
A Tarefa B inicia uma busca de inicialização para um nome de serviço.
launchd verifica se a tarefa está em execução e, se não estiver, a inicia.
A Tarefa A (o serviço) executa um check-in de inicialização (bootstrap_check_in()
). Aqui, o servidor de inicialização cria um DIREITO DE ENVIO, o retém e transfere o DIREITO DE RECEBER para a Tarefa A.
launchd duplica o DIREITO DE ENVIO e envia para a Tarefa B.
A Tarefa B gera uma nova porta com um direito de RECEBER e um DIREITO DE ENVIO, e dá o DIREITO DE ENVIO para a Tarefa A (o serviço) para que ela possa enviar mensagens para a TAREFA B (comunicação bidirecional).
No entanto, esse processo se aplica apenas a tarefas de sistema predefinidas. Tarefas não pertencentes ao sistema ainda operam conforme descrito originalmente, o que poderia potencialmente permitir a falsificação.
Portanto, o launchd nunca deve travar, ou o sistema inteiro travará.
Encontre mais informações aqui
A função mach_msg
, essencialmente uma chamada de sistema, é utilizada para enviar e receber mensagens Mach. A função requer que a mensagem seja enviada como argumento inicial. Esta mensagem deve começar com uma estrutura mach_msg_header_t
, seguida pelo conteúdo real da mensagem. A estrutura é definida da seguinte forma:
Os processos que possuem um direito de recebimento podem receber mensagens em uma porta Mach. Por outro lado, os remetentes recebem um direito de envio ou um direito de envio único. O direito de envio único é exclusivamente para enviar uma única mensagem, após o que se torna inválido.
O campo inicial msgh_bits
é um mapa de bits:
O primeiro bit (mais significativo) é usado para indicar que uma mensagem é complexa (mais sobre isso abaixo)
O 3º e o 4º são usados pelo kernel
Os 5 bits menos significativos do 2º byte podem ser usados para voucher: outro tipo de porta para enviar combinações de chave/valor.
Os 5 bits menos significativos do 3º byte podem ser usados para porta local
Os 5 bits menos significativos do 4º byte podem ser usados para porta remota
Os tipos que podem ser especificados no voucher, portas locais e remotas são (de mach/message.h):
Por exemplo, MACH_MSG_TYPE_MAKE_SEND_ONCE
pode ser usado para indicar que um direito de envio-único deve ser derivado e transferido para esta porta. Também pode ser especificado MACH_PORT_NULL
para impedir que o destinatário possa responder.
Para alcançar uma comunicação bidirecional fácil, um processo pode especificar uma porta mach no cabeçalho da mensagem mach chamada porta de resposta (msgh_local_port
) onde o receptor da mensagem pode enviar uma resposta a esta mensagem.
Note que esse tipo de comunicação bidirecional é usado em mensagens XPC que esperam uma resposta (xpc_connection_send_message_with_reply
e xpc_connection_send_message_with_reply_sync
). Mas geralmente são criadas portas diferentes como explicado anteriormente para criar a comunicação bidirecional.
Os outros campos do cabeçalho da mensagem são:
msgh_size
: o tamanho do pacote inteiro.
msgh_remote_port
: a porta para a qual esta mensagem é enviada.
msgh_voucher_port
: vouchers mach.
msgh_id
: o ID desta mensagem, que é interpretado pelo receptor.
Note que mensagens mach são enviadas por uma porta mach
, que é um canal de comunicação de um único receptor, múltiplos remetentes integrado no kernel mach. Múltiplos processos podem enviar mensagens para uma porta mach, mas em qualquer momento apenas um único processo pode ler dela.
As mensagens são então formadas pelo cabeçalho mach_msg_header_t
seguido do corpo e do trailer (se houver) e pode conceder permissão para responder a ela. Nestes casos, o kernel só precisa passar a mensagem de uma tarefa para a outra.
Um trailer é informação adicionada à mensagem pelo kernel (não pode ser definida pelo usuário) que pode ser solicitada na recepção da mensagem com as flags MACH_RCV_TRAILER_<trailer_opt>
(há diferentes informações que podem ser solicitadas).
No entanto, existem outras mensagens mais complexas, como as que passam direitos de porta adicionais ou compartilham memória, onde o kernel também precisa enviar esses objetos para o destinatário. Nestes casos, o bit mais significativo do cabeçalho msgh_bits
é definido.
Os descritores possíveis para passar são definidos em mach/message.h
:
Em 32 bits, todos os descritores têm 12 bytes e o tipo de descritor está no 11º byte. Em 64 bits, os tamanhos variam.
O kernel copiará os descritores de uma tarefa para a outra, mas primeiro criará uma cópia na memória do kernel. Essa técnica, conhecida como "Feng Shui", tem sido abusada em vários exploits para fazer o kernel copiar dados em sua memória, fazendo com que um processo envie descritores para si mesmo. Em seguida, o processo pode receber as mensagens (o kernel as liberará).
Também é possível enviar direitos de porta para um processo vulnerável, e os direitos da porta simplesmente aparecerão no processo (mesmo que ele não os esteja manipulando).
Observe que as portas estão associadas ao namespace da tarefa, então para criar ou procurar uma porta, o namespace da tarefa também é consultado (mais em mach/mach_port.h
):
mach_port_allocate
| mach_port_construct
: Criar uma porta.
mach_port_allocate
também pode criar um conjunto de portas: direito de recebimento sobre um grupo de portas. Sempre que uma mensagem é recebida, é indicada a porta de onde ela veio.
mach_port_allocate_name
: Alterar o nome da porta (por padrão, inteiro de 32 bits)
mach_port_names
: Obter nomes de porta de um alvo
mach_port_type
: Obter direitos de uma tarefa sobre um nome
mach_port_rename
: Renomear uma porta (como dup2 para FDs)
mach_port_allocate
: Alocar um novo RECEBER, CONJUNTO_DE_PORTAS ou DEAD_NAME
mach_port_insert_right
: Criar um novo direito em uma porta onde você tem RECEBER
mach_port_...
mach_msg
| mach_msg_overwrite
: Funções usadas para enviar e receber mensagens mach. A versão de sobrescrita permite especificar um buffer diferente para a recepção da mensagem (a outra versão apenas o reutilizará).
Como as funções mach_msg
e mach_msg_overwrite
são as usadas para enviar e receber mensagens, definir um ponto de interrupção nelas permitiria inspecionar as mensagens enviadas e recebidas.
Por exemplo, iniciar a depuração de qualquer aplicativo que você possa depurar, pois ele carregará libSystem.B
que usará essa função.
Para obter os argumentos de mach_msg
, verifique os registradores. Estes são os argumentos (de mach/message.h):
Obtenha os valores dos registros:
Verifique o cabeçalho da mensagem verificando o primeiro argumento:
Esse tipo de mach_msg_bits_t
é muito comum para permitir uma resposta.
O nome é o nome padrão dado à porta (verifique como ele está aumentando nos primeiros 3 bytes). O ipc-object
é o identificador único ofuscado da porta.
Observe também como as portas com apenas o direito de send
estão identificando o proprietário dela (nome da porta + pid).
Observe também o uso de +
para indicar outras tarefas conectadas à mesma porta.
Também é possível usar procesxp para ver também os nomes de serviço registrados (com SIP desativado devido à necessidade de com.apple.system-task-port
):
Pode instalar esta ferramenta no iOS fazendo o download em http://newosxbook.com/tools/binpack64-256.tar.gz
Observe como o remetente aloca uma porta, cria um direito de envio para o nome org.darlinghq.example
e o envia para o servidor de inicialização enquanto o remetente solicitou o direito de envio desse nome e o usou para enviar uma mensagem.
O sender.c
é um exemplo simples de um programa que envia mensagens para um receptor usando IPC (Comunicação entre Processos) no macOS. Ele demonstra como enviar mensagens através de uma fila de mensagens POSIX.
Isso enviará uma mensagem para o receptor.
Existem algumas portas especiais que permitem realizar certas ações sensíveis ou acessar determinados dados sensíveis caso uma tarefa tenha permissões de ENVIO sobre elas. Isso torna essas portas muito interessantes do ponto de vista de um atacante não apenas por causa das capacidades, mas também porque é possível compartilhar permissões de ENVIO entre tarefas.
Essas portas são representadas por um número.
Os direitos de ENVIO podem ser obtidos chamando host_get_special_port
e os direitos de RECEBIMENTO chamando host_set_special_port
. No entanto, ambas as chamadas requerem a porta host_priv
que apenas o root pode acessar. Além disso, no passado, o root era capaz de chamar host_set_special_port
e sequestrar arbitrariamente o que permitia, por exemplo, ignorar assinaturas de código sequestrando HOST_KEXTD_PORT
(SIP agora impede isso).
Essas portas são divididas em 2 grupos: Os primeiros 7 portas são de propriedade do kernel sendo o 1 HOST_PORT
, o 2 HOST_PRIV_PORT
, o 3 HOST_IO_MASTER_PORT
e o 7 é HOST_MAX_SPECIAL_KERNEL_PORT
.
Os que começam a partir do número 8 são de propriedade dos daemons do sistema e podem ser encontrados declarados em host_special_ports.h
.
Porta do Host: Se um processo tem privilégio de ENVIO sobre esta porta, ele pode obter informações sobre o sistema chamando suas rotinas como:
host_processor_info
: Obter informações do processador
host_info
: Obter informações do host
host_virtual_physical_table_info
: Tabela de páginas virtual/física (requer MACH_VMDEBUG)
host_statistics
: Obter estatísticas do host
mach_memory_info
: Obter layout de memória do kernel
Porta Priv do Host: Um processo com direito de ENVIO sobre esta porta pode realizar ações privilegiadas como mostrar dados de inicialização ou tentar carregar uma extensão de kernel. O processo precisa ser root para obter essa permissão.
Além disso, para chamar a API kext_request
é necessário ter outras permissões com.apple.private.kext*
que são concedidas apenas a binários da Apple.
Outras rotinas que podem ser chamadas são:
host_get_boot_info
: Obter machine_boot_info()
host_priv_statistics
: Obter estatísticas privilegiadas
vm_allocate_cpm
: Alocar Memória Física Contígua
host_processors
: Direito de envio para processadores do host
mach_vm_wire
: Tornar a memória residente
Como o root pode acessar essa permissão, ele poderia chamar host_set_[special/exception]_port[s]
para sequestrar portas especiais ou de exceção do host.
É possível ver todas as portas especiais do host executando:
Estas são portas reservadas para serviços conhecidos. É possível obtê-las/configurá-las chamando task_[get/set]_special_port
. Elas podem ser encontradas em task_special_ports.h
:
De aqui:
TASK_KERNEL_PORT[direito de envio de tarefa-self]: A porta usada para controlar esta tarefa. Usada para enviar mensagens que afetam a tarefa. Esta é a porta retornada por mach_task_self (veja Portas de Tarefa abaixo).
TASK_BOOTSTRAP_PORT[direito de envio de inicialização]: A porta de inicialização da tarefa. Usada para enviar mensagens solicitando o retorno de outras portas de serviço do sistema.
TASK_HOST_NAME_PORT[direito de envio de host-self]: A porta usada para solicitar informações do host contido. Esta é a porta retornada por mach_host_self.
TASK_WIRED_LEDGER_PORT[direito de envio de livro-razão]: A porta que nomeia a fonte da qual esta tarefa retira sua memória com fio do kernel.
TASK_PAGED_LEDGER_PORT[direito de envio de livro-razão]: A porta que nomeia a fonte da qual esta tarefa retira sua memória gerenciada por padrão.
Originalmente, o Mach não tinha "processos", tinha "tarefas", que eram consideradas mais como contêineres de threads. Quando o Mach foi mesclado com o BSD, cada tarefa foi correlacionada com um processo BSD. Portanto, cada processo BSD tem os detalhes necessários para ser um processo e cada tarefa Mach também tem seus mecanismos internos (exceto pelo pid inexistente 0, que é a kernel_task
).
Existem duas funções muito interessantes relacionadas a isso:
task_for_pid(porta_tarefa_alvo, pid, &porta_tarefa_do_pid)
: Obtenha um direito de envio para a porta da tarefa relacionada ao especificado pelo pid
e dê-o à porta_tarefa_alvo
indicada (que geralmente é a tarefa chamadora que usou mach_task_self()
, mas poderia ser uma porta de envio sobre uma tarefa diferente).
pid_for_task(tarefa, &pid)
: Dado um direito de envio para uma tarefa, descubra a qual PID essa tarefa está relacionada.
Para realizar ações dentro da tarefa, a tarefa precisava de um direito de envio para si mesma chamando mach_task_self()
(que usa o task_self_trap
(28)). Com essa permissão, uma tarefa pode realizar várias ações como:
task_threads
: Obter direitos de envio sobre todas as portas de tarefa das threads da tarefa
task_info
: Obter informações sobre uma tarefa
task_suspend/resume
: Suspender ou retomar uma tarefa
task_[get/set]_special_port
thread_create
: Criar uma thread
task_[get/set]_state
: Controlar o estado da tarefa
e mais podem ser encontrados em mach/task.h
Observe que com um direito de envio sobre uma porta de tarefa de uma tarefa diferente, é possível realizar tais ações sobre uma tarefa diferente.
Além disso, a porta da tarefa é também a porta vm_map
que permite ler e manipular memória dentro de uma tarefa com funções como vm_read()
e vm_write()
. Isso basicamente significa que uma tarefa com direitos de envio sobre a porta da tarefa de uma tarefa diferente será capaz de injetar código nessa tarefa.
Lembre-se de que porque o kernel também é uma tarefa, se alguém conseguir obter permissões de envio sobre o kernel_task
, será capaz de fazer o kernel executar qualquer coisa (jailbreaks).
Chame mach_task_self()
para obter o nome desta porta para a tarefa chamadora. Esta porta é herdada apenas através de exec()
; uma nova tarefa criada com fork()
obtém uma nova porta de tarefa (como caso especial, uma tarefa também obtém uma nova porta de tarefa após exec()
em um binário suid). A única maneira de gerar uma tarefa e obter sua porta é realizar a "dança de troca de portas" enquanto faz um fork()
.
Estas são as restrições para acessar a porta (de macos_task_policy
do binário AppleMobileFileIntegrity
):
Se o aplicativo tiver a permissão com.apple.security.get-task-allow
, processos do mesmo usuário podem acessar a porta da tarefa (comumente adicionado pelo Xcode para depuração). O processo de notarização não permitirá isso em lançamentos de produção.
Aplicativos com a permissão com.apple.system-task-ports
podem obter a porta da tarefa para qualquer processo, exceto o kernel. Em versões mais antigas, era chamado de task_for_pid-allow
. Isso é concedido apenas a aplicativos da Apple.
Root pode acessar portas de tarefas de aplicativos não compilados com um tempo de execução fortificado (e não da Apple).
A porta do nome da tarefa: Uma versão não privilegiada da porta da tarefa. Ela faz referência à tarefa, mas não permite controlá-la. A única coisa que parece estar disponível por meio dela é task_info()
.
As threads também têm portas associadas, que são visíveis da tarefa chamando task_threads
e do processador com processor_set_threads
. Um direito de envio para a porta da thread permite usar a função do subsistema thread_act
, como:
thread_terminate
thread_[get/set]_state
act_[get/set]_state
thread_[suspend/resume]
thread_info
...
Qualquer thread pode obter esta porta chamando mach_thread_sef
.
Você pode obter um shellcode de:
Introduction to ARM64v8A Comunicação entre Processos (IPC) é um mecanismo essencial para que os processos em um sistema operacional possam trocar informações e coordenar suas atividades. No macOS, existem várias formas de IPC, como notificações distribuídas, Apple Events, XPC e IPC baseado em porta.
Os processos no macOS podem abusar dos mecanismos de IPC para obter privilégios elevados ou realizar ações maliciosas. É importante entender como os processos legítimos usam a IPC e monitorar atividades suspeitas para identificar possíveis abusos.
Para proteger um sistema macOS contra abusos de IPC, é fundamental implementar práticas de segurança, como restringir as permissões de IPC por meio de arquivos de entitlements e monitorar o uso de IPC por processos suspeitos.
Ao compreender os mecanismos de IPC no macOS e adotar medidas proativas para proteger contra abusos, é possível fortalecer a segurança do sistema e reduzir o risco de escalonamento de privilégios e atividades maliciosas.
Compile o programa anterior e adicione os privilégios para poder injetar código com o mesmo usuário (caso contrário, será necessário usar sudo).
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)