Introduction to ARM64v8
Last updated
Last updated
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
In der ARMv8-Architektur definieren Ausführungsebenen, bekannt als Ausnahmeebenen (ELs), das Privilegieniveau und die Fähigkeiten der Ausführungsumgebung. Es gibt vier Ausnahmeebenen, die von EL0 bis EL3 reichen, wobei jede eine andere Funktion erfüllt:
EL0 - Benutzermodus:
Dies ist die am wenigsten privilegierte Ebene und wird zur Ausführung regulärer Anwendungssoftware verwendet.
Anwendungen, die auf EL0 ausgeführt werden, sind voneinander und von der Systemsoftware isoliert, was die Sicherheit und Stabilität erhöht.
EL1 - Betriebssystem-Kernelmodus:
Die meisten Betriebssystemkerne laufen auf dieser Ebene.
EL1 hat mehr Privilegien als EL0 und kann auf Systemressourcen zugreifen, jedoch mit einigen Einschränkungen, um die Systemintegrität zu gewährleisten.
EL2 - Hypervisor-Modus:
Diese Ebene wird für Virtualisierung verwendet. Ein Hypervisor, der auf EL2 läuft, kann mehrere Betriebssysteme (jeweils in ihrem eigenen EL1) verwalten, die auf derselben physischen Hardware laufen.
EL2 bietet Funktionen zur Isolation und Kontrolle der virtualisierten Umgebungen.
EL3 - Sicherer Monitor-Modus:
Dies ist die privilegierteste Ebene und wird häufig für sicheres Booten und vertrauenswürdige Ausführungsumgebungen verwendet.
EL3 kann Zugriffe zwischen sicheren und nicht sicheren Zuständen (wie sicherem Booten, vertrauenswürdigem OS usw.) verwalten und steuern.
Die Verwendung dieser Ebenen ermöglicht eine strukturierte und sichere Verwaltung verschiedener Aspekte des Systems, von Benutzeranwendungen bis hin zu den privilegiertesten Systemsoftware. Der Ansatz von ARMv8 zu Privilegienniveaus hilft, verschiedene Systemkomponenten effektiv zu isolieren, wodurch die Sicherheit und Robustheit des Systems erhöht wird.
ARM64 hat 31 allgemeine Register, die von x0
bis x30
beschriftet sind. Jedes kann einen 64-Bit (8-Byte) Wert speichern. Für Operationen, die nur 32-Bit-Werte erfordern, können dieselben Register im 32-Bit-Modus mit den Namen w0 bis w30 angesprochen werden.
x0
bis x7
- Diese werden typischerweise als Scratch-Register und zum Übergeben von Parametern an Unterprogramme verwendet.
x0
trägt auch die Rückgabedaten einer Funktion
x8
- Im Linux-Kernel wird x8
als Systemaufrufnummer für die svc
-Anweisung verwendet. In macOS wird x16 verwendet!
x9
bis x15
- Weitere temporäre Register, die oft für lokale Variablen verwendet werden.
x16
und x17
- Intra-prozedurale Aufrufregister. Temporäre Register für unmittelbare Werte. Sie werden auch für indirekte Funktionsaufrufe und PLT (Procedure Linkage Table) Stubs verwendet.
x16
wird als Systemaufrufnummer für die svc
-Anweisung in macOS verwendet.
x18
- Plattformregister. Es kann als allgemeines Register verwendet werden, aber auf einigen Plattformen ist dieses Register für plattformspezifische Zwecke reserviert: Zeiger auf den aktuellen Thread-Umgebungsblock in Windows oder um auf die aktuell ausführende Aufgabenstruktur im Linux-Kernel zu zeigen.
x19
bis x28
- Dies sind callee-saved Register. Eine Funktion muss die Werte dieser Register für ihren Aufrufer bewahren, sodass sie im Stack gespeichert und vor dem Zurückkehren zum Aufrufer wiederhergestellt werden.
x29
- Frame-Zeiger, um den Stackrahmen zu verfolgen. Wenn ein neuer Stackrahmen erstellt wird, weil eine Funktion aufgerufen wird, wird das x29
-Register im Stack gespeichert und die neue Frame-Zeigeradresse (Adresse von sp
) wird in diesem Register gespeichert.
Dieses Register kann auch als allgemeines Register verwendet werden, obwohl es normalerweise als Referenz für lokale Variablen verwendet wird.
x30
oder lr
- Link-Register. Es hält die Rückgabeadresse, wenn eine BL
(Branch with Link) oder BLR
(Branch with Link to Register) Anweisung ausgeführt wird, indem der pc
-Wert in diesem Register gespeichert wird.
Es könnte auch wie jedes andere Register verwendet werden.
Wenn die aktuelle Funktion eine neue Funktion aufrufen und daher lr
überschreiben wird, wird sie zu Beginn im Stack gespeichert, dies ist der Epilog (stp x29, x30 , [sp, #-48]; mov x29, sp
-> Speichern von fp
und lr
, Platz schaffen und neuen fp
erhalten) und am Ende wiederhergestellt, dies ist der Prolog (ldp x29, x30, [sp], #48; ret
-> Wiederherstellen von fp
und lr
und Rückkehr).
sp
- Stack-Zeiger, der verwendet wird, um den oberen Teil des Stacks zu verfolgen.
Der sp
-Wert sollte immer mindestens auf eine Quadword-Ausrichtung gehalten werden, da sonst eine Ausrichtungsfehler auftreten kann.
pc
- Program Counter, der auf die nächste Anweisung zeigt. Dieses Register kann nur durch Ausnahmeerzeugungen, Ausnahme-Rückgaben und Sprünge aktualisiert werden. Die einzigen gewöhnlichen Anweisungen, die dieses Register lesen können, sind Sprünge mit Link-Anweisungen (BL, BLR), um die pc
-Adresse im lr
(Link-Register) zu speichern.
xzr
- Null-Register. Auch in seiner 32-Bit-Registerform als wzr
bezeichnet. Kann verwendet werden, um den Nullwert einfach zu erhalten (häufige Operation) oder um Vergleiche mit subs
durchzuführen, wie subs XZR, Xn, #10
, wobei die resultierenden Daten nirgendwo gespeichert werden (in xzr
).
Die Wn
-Register sind die 32-Bit-Version der Xn
-Register.
Darüber hinaus gibt es weitere 32 Register mit einer Länge von 128 Bit, die in optimierten Single Instruction Multiple Data (SIMD)-Operationen und zur Durchführung von Gleitkomma-Arithmetik verwendet werden können. Diese werden als Vn-Register bezeichnet, obwohl sie auch in 64-Bit, 32-Bit, 16-Bit und 8-Bit betrieben werden können und dann als Qn
, Dn
, Sn
, Hn
und Bn
bezeichnet werden.
Es gibt Hunderte von Systemregistern, auch als spezielle Register (SPRs) bezeichnet, die zur Überwachung und Steuerung des Verhaltens von Prozessoren verwendet werden.
Sie können nur mit den speziellen Anweisungen mrs
und msr
gelesen oder gesetzt werden.
Die speziellen Register TPIDR_EL0
und TPIDDR_EL0
sind häufig beim Reverse Engineering zu finden. Der EL0
-Suffix zeigt die minimale Ausnahme an, von der aus das Register zugegriffen werden kann (in diesem Fall ist EL0 das reguläre Ausnahmeniveau (Privileg), mit dem reguläre Programme ausgeführt werden).
Sie werden oft verwendet, um die Basisadresse des thread-lokalen Speicherbereichs im Speicher zu speichern. In der Regel ist das erste für Programme, die in EL0 laufen, lesbar und schreibbar, aber das zweite kann von EL0 gelesen und von EL1 (wie Kernel) geschrieben werden.
mrs x0, TPIDR_EL0 ; Lese TPIDR_EL0 in x0
msr TPIDR_EL0, X0 ; Schreibe x0 in TPIDR_EL0
PSTATE enthält mehrere Prozesskomponenten, die in das vom Betriebssystem sichtbare SPSR_ELx
-Sonderregister serialisiert sind, wobei X das Berechtigungsniveau der ausgelösten Ausnahme angibt (dies ermöglicht es, den Prozesszustand wiederherzustellen, wenn die Ausnahme endet).
Dies sind die zugänglichen Felder:
Die N
, Z
, C
und V
-Bedingungsflags:
N
bedeutet, dass die Operation ein negatives Ergebnis geliefert hat
Z
bedeutet, dass die Operation null ergeben hat
C
bedeutet, dass die Operation einen Übertrag hatte
V
bedeutet, dass die Operation einen signierten Überlauf ergeben hat:
Die Summe von zwei positiven Zahlen ergibt ein negatives Ergebnis.
Die Summe von zwei negativen Zahlen ergibt ein positives Ergebnis.
Bei der Subtraktion, wenn eine große negative Zahl von einer kleineren positiven Zahl (oder umgekehrt) subtrahiert wird und das Ergebnis nicht im Bereich der gegebenen Bitgröße dargestellt werden kann.
Offensichtlich weiß der Prozessor nicht, ob die Operation signiert ist oder nicht, daher überprüft er C und V in den Operationen und zeigt an, ob ein Übertrag aufgetreten ist, falls es signiert oder nicht signiert war.
Nicht alle Anweisungen aktualisieren diese Flags. Einige wie CMP
oder TST
tun dies, und andere, die ein s-Suffix haben, wie ADDS
, tun es ebenfalls.
Das aktuelle Registerbreite (nRW
) Flag: Wenn das Flag den Wert 0 hat, wird das Programm im AArch64-Ausführungszustand fortgesetzt.
Das aktuelle Ausnahmeebene (EL
): Ein reguläres Programm, das in EL0 läuft, hat den Wert 0
Das Single-Stepping-Flag (SS
): Wird von Debuggern verwendet, um einen Schritt auszuführen, indem das SS-Flag auf 1 innerhalb von SPSR_ELx
durch eine Ausnahme gesetzt wird. Das Programm führt einen Schritt aus und löst eine Einzelstepp-Ausnahme aus.
Das illegal exception-Zustandsflag (IL
): Es wird verwendet, um zu kennzeichnen, wann eine privilegierte Software einen ungültigen Ausnahmeebenenübergang durchführt, dieses Flag wird auf 1 gesetzt und der Prozessor löst eine illegale Zustandsausnahme aus.
Die DAIF
-Flags: Diese Flags ermöglichen es einem privilegierten Programm, bestimmte externe Ausnahmen selektiv zu maskieren.
Wenn A
1 ist, bedeutet dies, dass asynchrone Abbrüche ausgelöst werden. Das I
konfiguriert die Reaktion auf externe Hardware-Interrupt-Anfragen (IRQs). und das F bezieht sich auf Fast Interrupt Requests (FIRs).
Die Stack-Zeiger-Auswahl-Flags (SPS
): Privilegierte Programme, die in EL1 und höher laufen, können zwischen der Verwendung ihres eigenen Stack-Zeiger-Registers und dem Benutzer-Modell-Register wechseln (z. B. zwischen SP_EL1
und EL0
). Dieser Wechsel erfolgt durch Schreiben in das spezielle Register SPSel
. Dies kann nicht von EL0 aus erfolgen.
Die ARM64-Aufrufkonvention legt fest, dass die ersten acht Parameter an eine Funktion in den Registern x0
bis x7
übergeben werden. Zusätzliche Parameter werden auf dem Stack übergeben. Der Rückgabewert wird im Register x0
oder in x1
zurückgegeben, wenn er 128 Bit lang ist. Die Register x19
bis x30
und sp
müssen bewahrt werden, wenn Funktionsaufrufe durchgeführt werden.
Beim Lesen einer Funktion in Assembler sollten Sie nach dem Funktionsprolog und -epilog suchen. Der Prolog umfasst normalerweise das Speichern des Frame-Zeigers (x29
), das Einrichten eines neuen Frame-Zeigers und das Zuweisen von Stackplatz. Der Epilog umfasst normalerweise das Wiederherstellen des gespeicherten Frame-Zeigers und das Zurückkehren von der Funktion.
Swift hat seine eigene Aufrufkonvention, die in https://github.com/apple/swift/blob/main/docs/ABI/CallConvSummary.rst#arm64 zu finden ist.
ARM64-Anweisungen haben im Allgemeinen das Format opcode dst, src1, src2
, wobei opcode
die auszuführende Operation (wie add
, sub
, mov
usw.) ist, dst
das Ziel-Register, in dem das Ergebnis gespeichert wird, und src1
und src2
die Quell-Register sind. Sofortwerte können auch anstelle von Quellregistern verwendet werden.
mov
: Bewege einen Wert von einem Register in ein anderes.
Beispiel: mov x0, x1
— Dies bewegt den Wert von x1
nach x0
.
ldr
: Lade einen Wert aus dem Speicher in ein Register.
Beispiel: ldr x0, [x1]
— Dies lädt einen Wert von der Speicheradresse, die von x1
angegeben wird, in x0
.
Offset-Modus: Ein Offset, der den ursprünglichen Zeiger beeinflusst, wird angegeben, zum Beispiel:
ldr x2, [x1, #8]
, dies lädt in x2 den Wert von x1 + 8
ldr x2, [x0, x1, lsl #2]
, dies lädt in x2 ein Objekt aus dem Array x0, von der Position x1 (Index) * 4
Pre-indexierter Modus: Dies wendet Berechnungen auf den Ursprung an, erhält das Ergebnis und speichert auch den neuen Ursprung im Ursprung.
ldr x2, [x1, #8]!
, dies lädt x1 + 8
in x2
und speichert das Ergebnis von x1 + 8
in x1
str lr, [sp, #-4]!
, Speichert das Link-Register in sp und aktualisiert das Register sp
Post-indexierter Modus: Dies ist wie der vorherige, aber die Speicheradresse wird zuerst zugegriffen und dann wird der Offset berechnet und gespeichert.
ldr x0, [x1], #8
, lade x1
in x0
und aktualisiere x1 mit x1 + 8
PC-relative Adressierung: In diesem Fall wird die zu ladende Adresse relativ zum PC-Register berechnet
ldr x1, =_start
, Dies lädt die Adresse, an der das _start
-Symbol beginnt, in x1 in Bezug auf den aktuellen PC.
str
: Speichere einen Wert aus einem Register in den Speicher.
Beispiel: str x0, [x1]
— Dies speichert den Wert in x0
an der Speicheradresse, die von x1
angegeben wird.
ldp
: Lade ein Paar von Registern. Diese Anweisung lädt zwei Register aus aufeinanderfolgenden Speicher-Adressen. Die Speicheradresse wird typischerweise gebildet, indem ein Offset zum Wert in einem anderen Register addiert wird.
Beispiel: ldp x0, x1, [x2]
— Dies lädt x0
und x1
von den Speicheradressen bei x2
und x2 + 8
.
stp
: Speichere ein Paar von Registern. Diese Anweisung speichert zwei Register in aufeinanderfolgende Speicher-Adressen. Die Speicheradresse wird typischerweise gebildet, indem ein Offset zum Wert in einem anderen Register addiert wird.
Beispiel: stp x0, x1, [sp]
— Dies speichert x0
und x1
an den Speicheradressen bei sp
und sp + 8
.
stp x0, x1, [sp, #16]!
— Dies speichert x0
und x1
an den Speicheradressen bei sp+16
und sp + 24
und aktualisiert sp
mit sp+16
.
add
: Addiere die Werte von zwei Registern und speichere das Ergebnis in einem Register.
Syntax: add(s) Xn1, Xn2, Xn3 | #imm, [shift #N | RRX]
Xn1 -> Ziel
Xn2 -> Operand 1
Xn3 | #imm -> Operand 2 (Register oder sofort)
[shift #N | RRX] -> Führe eine Verschiebung durch oder rufe RRX auf
Beispiel: add x0, x1, x2
— Dies addiert die Werte in x1
und x2
und speichert das Ergebnis in x0
.
add x5, x5, #1, lsl #12
— Dies entspricht 4096 (ein 1-Verschieber 12 Mal) -> 1 0000 0000 0000 0000
adds
Dies führt ein add
aus und aktualisiert die Flags
sub
: Subtrahiere die Werte von zwei Registern und speichere das Ergebnis in einem Register.
Überprüfen Sie die add
Syntax.
Beispiel: sub x0, x1, x2
— Dies subtrahiert den Wert in x2
von x1
und speichert das Ergebnis in x0
.
subs
Dies ist wie sub, aktualisiert jedoch das Flag
mul
: Multipliziere die Werte von zwei Registern und speichere das Ergebnis in einem Register.
Beispiel: mul x0, x1, x2
— Dies multipliziert die Werte in x1
und x2
und speichert das Ergebnis in x0
.
div
: Dividiere den Wert eines Registers durch einen anderen und speichere das Ergebnis in einem Register.
Beispiel: div x0, x1, x2
— Dies dividiert den Wert in x1
durch x2
und speichert das Ergebnis in x0
.
lsl
, lsr
, asr
, ror
, rrx
:
Logische Verschiebung nach links: Füge 0en am Ende hinzu, indem die anderen Bits nach vorne verschoben werden (multipliziere mit n-mal 2)
Logische Verschiebung nach rechts: Füge 1en am Anfang hinzu, indem die anderen Bits nach hinten verschoben werden (dividiere mit n-mal 2 in unsigned)
Arithmetische Verschiebung nach rechts: Wie lsr
, aber anstelle von 0en, wenn das bedeutendste Bit eine 1 ist, werden 1en hinzugefügt (teile mit n-mal 2 in signed)
Rechtsrotation: Wie lsr
, aber was von rechts entfernt wird, wird links angehängt
Rechtsrotation mit Erweiterung: Wie ror
, aber mit dem Übertragsflag als "bedeutendstes Bit". Das Übertragsflag wird auf das Bit 31 verschoben und das entfernte Bit zum Übertragsflag.
bfm
: Bitfeldverschiebung, diese Operationen kopieren Bits 0...n
von einem Wert und platzieren sie in den Positionen m..m+n
. Das #s
gibt die linkeste Bitposition an und #r
die Verschiebemenge nach rechts.
Bitfeldverschiebung: BFM Xd, Xn, #r
Signierte Bitfeldverschiebung: SBFM Xd, Xn, #r, #s
Unsigned Bitfeldverschiebung: UBFM Xd, Xn, #r, #s
Bitfeld extrahieren und einfügen: Kopiere ein Bitfeld von einem Register und kopiere es in ein anderes Register.
BFI X1, X2, #3, #4
Füge 4 Bits von X2 ab dem 3. Bit von X1 ein
BFXIL X1, X2, #3, #4
Extrahiere von dem 3. Bit von X2 vier Bits und kopiere sie nach X1
SBFIZ X1, X2, #3, #4
Signerweitere 4 Bits von X2 und füge sie in X1 ein, beginnend bei Bitposition 3, wobei die rechten Bits auf 0 gesetzt werden
SBFX X1, X2, #3, #4
Extrahiert 4 Bits, die bei Bit 3 von X2 beginnen, signiert sie und platziert das Ergebnis in X1
UBFIZ X1, X2, #3, #4
Nullerweitert 4 Bits von X2 und fügt sie in X1 ein, beginnend bei Bitposition 3, wobei die rechten Bits auf 0 gesetzt werden
UBFX X1, X2, #3, #4
Extrahiert 4 Bits, die bei Bit 3 von X2 beginnen, und platziert das nullerweitere Ergebnis in X1.
Sign Extend To X: Erweitert das Vorzeichen (oder fügt einfach 0en in der unsigned-Version hinzu) eines Wertes, um Operationen damit durchführen zu können:
SXTB X1, W2
Erweitert das Vorzeichen eines Bytes von W2 nach X1 (W2
ist die Hälfte von X2
) um die 64 Bits zu füllen
SXTH X1, W2
Erweitert das Vorzeichen einer 16-Bit-Zahl von W2 nach X1 um die 64 Bits zu füllen
SXTW X1, W2
Erweitert das Vorzeichen eines Bytes von W2 nach X1 um die 64 Bits zu füllen
UXTB X1, W2
Fügt 0en (unsigned) zu einem Byte von W2 nach X1 hinzu, um die 64 Bits zu füllen
extr
: Extrahiert Bits aus einem angegebenen Paar von zusammengefügten Registern.
Beispiel: EXTR W3, W2, W1, #3
Dies wird W1+W2 zusammenfügen und von Bit 3 von W2 bis Bit 3 von W1 extrahieren und in W3 speichern.
cmp
: Vergleiche zwei Register und setze Bedingungsflags. Es ist ein Alias von subs
, der das Zielregister auf das Nullregister setzt. Nützlich, um zu wissen, ob m == n
.
Es unterstützt die gleiche Syntax wie subs
Beispiel: cmp x0, x1
— Dies vergleicht die Werte in x0
und x1
und setzt die Bedingungsflags entsprechend.
cmn
: Vergleiche negative Operanden. In diesem Fall ist es ein Alias von adds
und unterstützt die gleiche Syntax. Nützlich, um zu wissen, ob m == -n
.
ccmp
: Bedingter Vergleich, es ist ein Vergleich, der nur durchgeführt wird, wenn ein vorheriger Vergleich wahr war und speziell die nzcv-Bits setzt.
cmp x1, x2; ccmp x3, x4, 0, NE; blt _func
-> wenn x1 != x2 und x3 < x4, springe zu func
Dies liegt daran, dass ccmp
nur ausgeführt wird, wenn der vorherige cmp
ein NE
war, wenn nicht, werden die Bits nzcv
auf 0 gesetzt (was den blt
-Vergleich nicht erfüllt).
Dies kann auch als ccmn
verwendet werden (gleich, aber negativ, wie cmp
vs cmn
).
tst
: Überprüft, ob einer der Werte des Vergleichs beide 1 sind (es funktioniert wie ein ANDS, ohne das Ergebnis irgendwo zu speichern). Es ist nützlich, um ein Register mit einem Wert zu überprüfen und zu sehen, ob eines der Bits des Registers, das im Wert angegeben ist, 1 ist.
Beispiel: tst X1, #7
Überprüfe, ob eines der letzten 3 Bits von X1 1 ist
teq
: XOR-Operation, die das Ergebnis verwirft
b
: Unbedingter Sprung
Beispiel: b myFunction
Beachten Sie, dass dies das Link-Register nicht mit der Rückgabeadresse füllt (nicht geeignet für Unterprogrammaufrufe, die zurückkehren müssen)
bl
: Sprung mit Link, verwendet, um ein Unterprogramm aufzurufen. Speichert die Rückgabeadresse in x30
.
Beispiel: bl myFunction
— Dies ruft die Funktion myFunction
auf und speichert die Rückgabeadresse in x30
.
Beachten Sie, dass dies das Link-Register nicht mit der Rückgabeadresse füllt (nicht geeignet für Unterprogrammaufrufe, die zurückkehren müssen)
blr
: Sprung mit Link zu Register, verwendet, um ein Unterprogramm aufzurufen, bei dem das Ziel in einem Register angegeben ist. Speichert die Rückgabeadresse in x30
. (Dies ist
Beispiel: blr x1
— Dies ruft die Funktion auf, deren Adresse in x1
enthalten ist, und speichert die Rückgabeadresse in x30
.
ret
: Rückkehr aus dem Unterprogramm, typischerweise unter Verwendung der Adresse in x30
.
Beispiel: ret
— Dies kehrt aus dem aktuellen Unterprogramm unter Verwendung der Rückgabeadresse in x30
zurück.
b.<cond>
: Bedingte Sprünge
b.eq
: Sprung, wenn gleich, basierend auf der vorherigen cmp
-Anweisung.
Beispiel: b.eq label
— Wenn die vorherige cmp
-Anweisung zwei gleiche Werte gefunden hat, springt dies zu label
.
b.ne
: Sprung, wenn nicht gleich. Diese Anweisung überprüft die Bedingungsflags (die von einer vorherigen Vergleichsanweisung gesetzt wurden), und wenn die verglichenen Werte nicht gleich waren, springt sie zu einem Label oder einer Adresse.
Beispiel: Nach einer cmp x0, x1
-Anweisung springt b.ne label
— Wenn die Werte in x0
und x1
nicht gleich waren, springt dies zu label
.
cbz
: Vergleiche und springe bei Null. Diese Anweisung vergleicht ein Register mit Null, und wenn sie gleich sind, springt sie zu einem Label oder einer Adresse.
Beispiel: cbz x0, label
— Wenn der Wert in x0
null ist, springt dies zu label
.
cbnz
: Vergleiche und springe bei Nicht-Null. Diese Anweisung vergleicht ein Register mit Null, und wenn sie nicht gleich sind, springt sie zu einem Label oder einer Adresse.
Beispiel: cbnz x0, label
— Wenn der Wert in x0
nicht null ist, springt dies zu label
.
tbnz
: Teste Bit und springe bei Nicht-Null
Beispiel: tbnz x0, #8, label
tbz
: Teste Bit und springe bei Null
Beispiel: tbz x0, #8, label
Bedingte Auswahloperationen: Dies sind Operationen, deren Verhalten je nach den bedingten Bits variiert.
csel Xd, Xn, Xm, cond
-> csel X0, X1, X2, EQ
-> Wenn wahr, X0 = X1, wenn falsch, X0 = X2
csinc Xd, Xn, Xm, cond
-> Wenn wahr, Xd = Xn, wenn falsch, Xd = Xm + 1
cinc Xd, Xn, cond
-> Wenn wahr, Xd = Xn + 1, wenn falsch, Xd = Xn
csinv Xd, Xn, Xm, cond
-> Wenn wahr, Xd = Xn, wenn falsch, Xd = NOT(Xm)
cinv Xd, Xn, cond
-> Wenn wahr, Xd = NOT(Xn), wenn falsch, Xd = Xn
csneg Xd, Xn, Xm, cond
-> Wenn wahr, Xd = Xn, wenn falsch, Xd = - Xm
cneg Xd, Xn, cond
-> Wenn wahr, Xd = - Xn, wenn falsch, Xd = Xn
cset Xd, Xn, Xm, cond
-> Wenn wahr, Xd = 1, wenn falsch, Xd = 0
csetm Xd, Xn, Xm, cond
-> Wenn wahr, Xd = <alle 1>, wenn falsch, Xd = 0
adrp
: Berechne die Seitenadresse eines Symbols und speichere sie in einem Register.
Beispiel: adrp x0, symbol
— Dies berechnet die Seitenadresse von symbol
und speichert sie in x0
.
ldrsw
: Lade einen signierten 32-Bit-Wert aus dem Speicher und signiere ihn auf 64 Bits.
Beispiel: ldrsw x0, [x1]
— Dies lädt einen signierten 32-Bit-Wert von der Speicheradresse, die von x1
angegeben wird, signiert ihn auf 64 Bits und speichert ihn in x0
.
stur
: Speichere einen Registerwert an einer Speicheradresse, unter Verwendung eines Offsets von einem anderen Register.
Beispiel: stur x0, [x1, #4]
— Dies speichert den Wert in x0
an der Speicheradresse, die 4 Bytes größer ist als die Adresse, die derzeit in x1
steht.
svc
: Führe einen Systemaufruf aus. Es steht für "Supervisor Call". Wenn der Prozessor diese Anweisung ausführt, wechselt er vom Benutzermodus in den Kernelmodus und springt zu einem bestimmten Ort im Speicher, an dem sich der Systemaufrufbehandlungs-Code des Kernels befindet.
Beispiel:
Speichern Sie das Link-Register und den Frame-Zeiger im Stack:
Richten Sie den neuen Frame-Zeiger ein: mov x29, sp
(richtet den neuen Frame-Zeiger für die aktuelle Funktion ein)
Reservieren Sie Platz auf dem Stack für lokale Variablen (falls erforderlich): sub sp, sp, <size>
(wobei <size>
die Anzahl der benötigten Bytes ist)
Geben Sie lokale Variablen frei (falls welche zugewiesen wurden): add sp, sp, <size>
Stellen Sie das Link-Register und den Frame-Zeiger wieder her:
Rückgabe: ret
(gibt die Kontrolle an den Aufrufer zurück, indem die Adresse im Link-Register verwendet wird)
Armv8-A unterstützt die Ausführung von 32-Bit-Programmen. AArch32 kann in einem von zwei Befehlssätzen ausgeführt werden: A32
und T32
und kann zwischen ihnen über interworking
wechseln.
Privilegierte 64-Bit-Programme können die Ausführung von 32-Bit-Programmen planen, indem sie einen Ausnahmeebenenübergang zur weniger privilegierten 32-Bit-Ebene ausführen.
Beachten Sie, dass der Übergang von 64-Bit zu 32-Bit mit einer Senkung der Ausnahmeebene erfolgt (zum Beispiel ein 64-Bit-Programm in EL1, das ein Programm in EL0 auslöst). Dies geschieht, indem das Bit 4 von SPSR_ELx
-Sonderregister auf 1 gesetzt wird, wenn der AArch32
-Prozess-Thread bereit ist, ausgeführt zu werden, und der Rest von SPSR_ELx
speichert die AArch32
-Programme CPSR. Dann ruft der privilegierte Prozess die ERET
-Anweisung auf, damit der Prozessor zu AArch32
wechselt und je nach CPSR in A32 oder T32 eintritt.**
Das interworking
erfolgt unter Verwendung der J- und T-Bits von CPSR. J=0
und T=0
bedeutet A32
und J=0
und T=1
bedeutet T32. Dies bedeutet im Wesentlichen, dass das niedrigste Bit auf 1 gesetzt wird, um anzuzeigen, dass der Befehlssatz T32 ist.
Dies wird während der interworking-Zweiganweisungen gesetzt, kann aber auch direkt mit anderen Anweisungen gesetzt werden, wenn der PC als Zielregister festgelegt ist. Beispiel:
Ein weiteres Beispiel:
Es gibt 16 32-Bit-Register (r0-r15). Von r0 bis r14 können sie für jede Operation verwendet werden, jedoch sind einige von ihnen normalerweise reserviert:
r15
: Programmzähler (immer). Enthält die Adresse der nächsten Anweisung. In A32 aktuell + 8, in T32, aktuell + 4.
r11
: Frame Pointer
r12
: Intra-prozeduraler Aufrufregister
r13
: Stack Pointer
r14
: Link Register
Darüber hinaus werden Register in banked registries
gesichert. Dies sind Orte, die die Registerwerte speichern und einen schnellen Kontextwechsel bei der Ausnahmebehandlung und privilegierten Operationen ermöglichen, um die Notwendigkeit zu vermeiden, Register jedes Mal manuell zu speichern und wiederherzustellen.
Dies geschieht durch Speichern des Prozessorstatus von CPSR
in SPSR
des Prozessormodus, in den die Ausnahme auftritt. Bei der Rückkehr von der Ausnahme wird der CPSR
aus dem SPSR
wiederhergestellt.
In AArch32 funktioniert der CPSR ähnlich wie PSTATE
in AArch64 und wird auch in SPSR_ELx
gespeichert, wenn eine Ausnahme auftritt, um die Ausführung später wiederherzustellen:
Die Felder sind in einige Gruppen unterteilt:
Anwendungsprogrammstatusregister (APSR): Arithmetische Flags und zugänglich von EL0
Ausführungsstatusregister: Prozessverhalten (verwaltet vom OS).
Die N
, Z
, C
, V
Flags (genau wie in AArch64)
Das Q
Flag: Es wird auf 1 gesetzt, wann immer ganzzahlige Sättigung auftritt während der Ausführung einer spezialisierten saturierenden arithmetischen Anweisung. Sobald es auf 1
gesetzt ist, behält es den Wert, bis es manuell auf 0 gesetzt wird. Darüber hinaus gibt es keine Anweisung, die seinen Wert implizit überprüft, dies muss manuell gelesen werden.
GE
(Größer oder gleich) Flags: Es wird in SIMD (Single Instruction, Multiple Data) Operationen verwendet, wie "parallele Addition" und "parallele Subtraktion". Diese Operationen ermöglichen die Verarbeitung mehrerer Datenpunkte in einer einzigen Anweisung.
Zum Beispiel addiert die UADD8
Anweisung vier Byte-Paare (von zwei 32-Bit-Operanden) parallel und speichert die Ergebnisse in einem 32-Bit-Register. Sie setzt dann die GE
Flags im APSR
basierend auf diesen Ergebnissen. Jedes GE-Flag entspricht einer der Byte-Addition, die angibt, ob die Addition für dieses Byte-Paar übergelaufen ist.
Die SEL
Anweisung verwendet diese GE-Flags, um bedingte Aktionen auszuführen.
Die J
und T
Bits: J
sollte 0 sein und wenn T
0 ist, wird der Befehlssatz A32 verwendet, und wenn er 1 ist, wird T32 verwendet.
IT Block Status Register (ITSTATE
): Dies sind die Bits von 10-15 und 25-26. Sie speichern Bedingungen für Anweisungen innerhalb einer IT
-präfixierten Gruppe.
E
Bit: Gibt die Endianness an.
Mode und Ausnahme-Maskenbits (0-4): Sie bestimmen den aktuellen Ausführungsstatus. Das 5. gibt an, ob das Programm als 32-Bit (eine 1) oder 64-Bit (eine 0) läuft. Die anderen 4 repräsentieren den aktuell verwendeten Ausnahme-Modus (wenn eine Ausnahme auftritt und behandelt wird). Die gesetzte Zahl zeigt die aktuelle Priorität an, falls eine andere Ausnahme ausgelöst wird, während diese behandelt wird.
AIF
: Bestimmte Ausnahmen können mit den Bits A
, I
, F
deaktiviert werden. Wenn A
1 ist, bedeutet das, dass asynchrone Abbrüche ausgelöst werden. Das I
konfiguriert die Reaktion auf externe Hardware Interrupts Requests (IRQs). und das F bezieht sich auf Fast Interrupt Requests (FIRs).
Schau dir syscalls.master an. BSD syscalls haben x16 > 0.
Schau dir in syscall_sw.c die mach_trap_table
und in mach_traps.h die Prototypen an. Die maximale Anzahl der Mach-Traps ist MACH_TRAP_TABLE_COUNT
= 128. Mach-Traps haben x16 < 0, also musst du die Nummern aus der vorherigen Liste mit einem Minus aufrufen: _kernelrpc_mach_vm_allocate_trap
ist -10
.
Du kannst auch libsystem_kernel.dylib
in einem Disassembler überprüfen, um herauszufinden, wie man diese (und BSD) syscalls aufruft:
Beachten Sie, dass Ida und Ghidra auch spezifische dylibs aus dem Cache dekompilieren können, indem sie einfach den Cache übergeben.
Manchmal ist es einfacher, den dekompilierten Code von libsystem_kernel.dylib
zu überprüfen, als den Quellcode zu überprüfen, da der Code mehrerer Syscalls (BSD und Mach) über Skripte generiert wird (siehe Kommentare im Quellcode), während Sie in der dylib finden können, was aufgerufen wird.
XNU unterstützt eine andere Art von Aufrufen, die maschinenabhängig sind. Die Anzahl dieser Aufrufe hängt von der Architektur ab, und weder die Aufrufe noch die Zahlen sind garantiert konstant.
Dies ist eine vom Kernel verwaltete Speicherseite, die in den Adressraum jedes Benutzerprozesses gemappt ist. Sie soll den Übergang vom Benutzermodus in den Kernelraum schneller machen, als es bei Syscalls für Kernel-Dienste der Fall wäre, die so häufig verwendet werden, dass dieser Übergang sehr ineffizient wäre.
Zum Beispiel liest der Aufruf gettimeofdate
den Wert von timeval
direkt von der comm-Seite.
Es ist sehr häufig, diese Funktion in Objective-C- oder Swift-Programmen zu finden. Diese Funktion ermöglicht es, eine Methode eines Objective-C-Objekts aufzurufen.
Parameter (weitere Informationen in den Dokumenten):
x0: self -> Zeiger auf die Instanz
x1: op -> Selektor der Methode
x2... -> Rest der Argumente der aufgerufenen Methode
Wenn Sie also einen Breakpoint vor dem Sprung zu dieser Funktion setzen, können Sie leicht herausfinden, was in lldb aufgerufen wird (in diesem Beispiel ruft das Objekt ein Objekt von NSConcreteTask
auf, das einen Befehl ausführen wird):
Durch das Setzen der Umgebungsvariable NSObjCMessageLoggingEnabled=1
ist es möglich, zu protokollieren, wann diese Funktion in einer Datei wie /tmp/msgSends-pid
aufgerufen wird.
Darüber hinaus kann durch das Setzen von OBJC_HELP=1
und das Aufrufen einer beliebigen Binärdatei andere Umgebungsvariablen angezeigt werden, die verwendet werden können, um log zu protokollieren, wann bestimmte Objc-C-Aktionen auftreten.
Wenn diese Funktion aufgerufen wird, ist es notwendig, die aufgerufene Methode der angegebenen Instanz zu finden. Dazu werden verschiedene Suchen durchgeführt:
Führen Sie eine optimistische Cache-Suche durch:
Wenn erfolgreich, fertig
Erwerben Sie runtimeLock (lesen)
Wenn (realize && !cls->realized) Klasse realisieren
Wenn (initialize && !cls->initialized) Klasse initialisieren
Versuchen Sie den eigenen Cache der Klasse:
Wenn erfolgreich, fertig
Versuchen Sie die Methodenliste der Klasse:
Wenn gefunden, Cache füllen und fertig
Versuchen Sie den Cache der Superklasse:
Wenn erfolgreich, fertig
Versuchen Sie die Methodenliste der Superklasse:
Wenn gefunden, Cache füllen und fertig
Wenn (resolver) versuchen Sie den Methodenresolver und wiederholen Sie die Suche von der Klassenlookup
Wenn Sie immer noch hier sind (= alles andere ist fehlgeschlagen) versuchen Sie den Forwarder
Um zu kompilieren:
Um die Bytes zu extrahieren:
Für neuere macOS:
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)