Introduction to ARM64v8
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Ausnahmeebenen - EL (ARM64v8)
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.
Register (ARM64v8)
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
bisx7
- 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 wirdx8
als Systemaufrufnummer für diesvc
-Anweisung verwendet. In macOS wird x16 verwendet!x9
bisx15
- Weitere temporäre Register, die oft für lokale Variablen verwendet werden.x16
undx17
- 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 diesvc
-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
bisx28
- 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 dasx29
-Register im Stack gespeichert und die neue Frame-Zeigeradresse (Adresse vonsp
) 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
oderlr
- Link-Register. Es hält die Rückgabeadresse, wenn eineBL
(Branch with Link) oderBLR
(Branch with Link to Register) Anweisung ausgeführt wird, indem derpc
-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 vonfp
undlr
, Platz schaffen und neuenfp
erhalten) und am Ende wiederhergestellt, dies ist der Prolog (ldp x29, x30, [sp], #48; ret
-> Wiederherstellen vonfp
undlr
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 diepc
-Adresse imlr
(Link-Register) zu speichern.xzr
- Null-Register. Auch in seiner 32-Bit-Registerform alswzr
bezeichnet. Kann verwendet werden, um den Nullwert einfach zu erhalten (häufige Operation) oder um Vergleiche mitsubs
durchzuführen, wiesubs XZR, Xn, #10
, wobei die resultierenden Daten nirgendwo gespeichert werden (inxzr
).
Die Wn
-Register sind die 32-Bit-Version der Xn
-Register.
SIMD- und Gleitkomma-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.
Systemregister
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
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
undV
-Bedingungsflags:N
bedeutet, dass die Operation ein negatives Ergebnis geliefert hatZ
bedeutet, dass die Operation null ergeben hatC
bedeutet, dass die Operation einen Übertrag hatteV
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 0Das Single-Stepping-Flag (
SS
): Wird von Debuggern verwendet, um einen Schritt auszuführen, indem das SS-Flag auf 1 innerhalb vonSPSR_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. DasI
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. zwischenSP_EL1
undEL0
). Dieser Wechsel erfolgt durch Schreiben in das spezielle RegisterSPSel
. Dies kann nicht von EL0 aus erfolgen.
Aufrufkonvention (ARM64v8)
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.
Aufrufkonvention in Swift
Swift hat seine eigene Aufrufkonvention, die in https://github.com/apple/swift/blob/main/docs/ABI/CallConvSummary.rst#arm64 zu finden ist.
Häufige Anweisungen (ARM64v8)
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 vonx1
nachx0
.ldr
: Lade einen Wert aus dem Speicher in ein Register.Beispiel:
ldr x0, [x1]
— Dies lädt einen Wert von der Speicheradresse, die vonx1
angegeben wird, inx0
.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 + 8ldr x2, [x0, x1, lsl #2]
, dies lädt in x2 ein Objekt aus dem Array x0, von der Position x1 (Index) * 4Pre-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ädtx1 + 8
inx2
und speichert das Ergebnis vonx1 + 8
inx1
str lr, [sp, #-4]!
, Speichert das Link-Register in sp und aktualisiert das Register spPost-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
, ladex1
inx0
und aktualisiere x1 mitx1 + 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 inx0
an der Speicheradresse, die vonx1
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ädtx0
undx1
von den Speicheradressen beix2
undx2 + 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 speichertx0
undx1
an den Speicheradressen beisp
undsp + 8
.stp x0, x1, [sp, #16]!
— Dies speichertx0
undx1
an den Speicheradressen beisp+16
undsp + 24
und aktualisiertsp
mitsp+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 inx1
undx2
und speichert das Ergebnis inx0
.add x5, x5, #1, lsl #12
— Dies entspricht 4096 (ein 1-Verschieber 12 Mal) -> 1 0000 0000 0000 0000adds
Dies führt einadd
aus und aktualisiert die Flagssub
: 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 inx2
vonx1
und speichert das Ergebnis inx0
.subs
Dies ist wie sub, aktualisiert jedoch das Flagmul
: Multipliziere die Werte von zwei Registern und speichere das Ergebnis in einem Register.Beispiel:
mul x0, x1, x2
— Dies multipliziert die Werte inx1
undx2
und speichert das Ergebnis inx0
.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 inx1
durchx2
und speichert das Ergebnis inx0
.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ängtRechtsrotation 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 Bits0...n
von einem Wert und platzieren sie in den Positionenm..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 einBFXIL X1, X2, #3, #4
Extrahiere von dem 3. Bit von X2 vier Bits und kopiere sie nach X1SBFIZ 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 werdenSBFX X1, X2, #3, #4
Extrahiert 4 Bits, die bei Bit 3 von X2 beginnen, signiert sie und platziert das Ergebnis in X1UBFIZ 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 werdenUBFX 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 vonX2
) um die 64 Bits zu füllenSXTH X1, W2
Erweitert das Vorzeichen einer 16-Bit-Zahl von W2 nach X1 um die 64 Bits zu füllenSXTW X1, W2
Erweitert das Vorzeichen eines Bytes von W2 nach X1 um die 64 Bits zu füllenUXTB X1, W2
Fügt 0en (unsigned) zu einem Byte von W2 nach X1 hinzu, um die 64 Bits zu füllenextr
: 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 vonsubs
, der das Zielregister auf das Nullregister setzt. Nützlich, um zu wissen, obm == n
.Es unterstützt die gleiche Syntax wie
subs
Beispiel:
cmp x0, x1
— Dies vergleicht die Werte inx0
undx1
und setzt die Bedingungsflags entsprechend.cmn
: Vergleiche negative Operanden. In diesem Fall ist es ein Alias vonadds
und unterstützt die gleiche Syntax. Nützlich, um zu wissen, obm == -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 funcDies liegt daran, dass
ccmp
nur ausgeführt wird, wenn der vorherigecmp
einNE
war, wenn nicht, werden die Bitsnzcv
auf 0 gesetzt (was denblt
-Vergleich nicht erfüllt).Dies kann auch als
ccmn
verwendet werden (gleich, aber negativ, wiecmp
vscmn
).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 istteq
: XOR-Operation, die das Ergebnis verwirftb
: Unbedingter SprungBeispiel:
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 inx30
.Beispiel:
bl myFunction
— Dies ruft die FunktionmyFunction
auf und speichert die Rückgabeadresse inx30
.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 inx30
. (Dies istBeispiel:
blr x1
— Dies ruft die Funktion auf, deren Adresse inx1
enthalten ist, und speichert die Rückgabeadresse inx30
.ret
: Rückkehr aus dem Unterprogramm, typischerweise unter Verwendung der Adresse inx30
.Beispiel:
ret
— Dies kehrt aus dem aktuellen Unterprogramm unter Verwendung der Rückgabeadresse inx30
zurück.b.<cond>
: Bedingte Sprüngeb.eq
: Sprung, wenn gleich, basierend auf der vorherigencmp
-Anweisung.Beispiel:
b.eq label
— Wenn die vorherigecmp
-Anweisung zwei gleiche Werte gefunden hat, springt dies zulabel
.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 springtb.ne label
— Wenn die Werte inx0
undx1
nicht gleich waren, springt dies zulabel
.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 inx0
null ist, springt dies zulabel
.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 inx0
nicht null ist, springt dies zulabel
.tbnz
: Teste Bit und springe bei Nicht-NullBeispiel:
tbnz x0, #8, label
tbz
: Teste Bit und springe bei NullBeispiel:
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 = X2csinc Xd, Xn, Xm, cond
-> Wenn wahr, Xd = Xn, wenn falsch, Xd = Xm + 1cinc Xd, Xn, cond
-> Wenn wahr, Xd = Xn + 1, wenn falsch, Xd = Xncsinv 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 = Xncsneg Xd, Xn, Xm, cond
-> Wenn wahr, Xd = Xn, wenn falsch, Xd = - Xmcneg Xd, Xn, cond
-> Wenn wahr, Xd = - Xn, wenn falsch, Xd = Xncset Xd, Xn, Xm, cond
-> Wenn wahr, Xd = 1, wenn falsch, Xd = 0csetm Xd, Xn, Xm, cond
-> Wenn wahr, Xd = <alle 1>, wenn falsch, Xd = 0adrp
: Berechne die Seitenadresse eines Symbols und speichere sie in einem Register.Beispiel:
adrp x0, symbol
— Dies berechnet die Seitenadresse vonsymbol
und speichert sie inx0
.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 vonx1
angegeben wird, signiert ihn auf 64 Bits und speichert ihn inx0
.stur
: Speichere einen Registerwert an einer Speicheradresse, unter Verwendung eines Offsets von einem anderen Register.Beispiel:
stur x0, [x1, #4]
— Dies speichert den Wert inx0
an der Speicheradresse, die 4 Bytes größer ist als die Adresse, die derzeit inx1
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:
Funktionsprolog
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)
Funktions-Epilog
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)
AARCH32 Ausführungszustand
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:
Register
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 Pointerr12
: Intra-prozeduraler Aufrufregisterr13
: Stack Pointerr14
: 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.
CPSR - Aktueller Programmstatusregister
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).
Anwendungsprogrammstatusregister (APSR)
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 auf1
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.
Ausführungsstatusregister
Die
J
undT
Bits:J
sollte 0 sein und wennT
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 einerIT
-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 BitsA
,I
,F
deaktiviert werden. WennA
1 ist, bedeutet das, dass asynchrone Abbrüche ausgelöst werden. DasI
konfiguriert die Reaktion auf externe Hardware Interrupts Requests (IRQs). und das F bezieht sich auf Fast Interrupt Requests (FIRs).
macOS
BSD syscalls
Schau dir syscalls.master an. BSD syscalls haben x16 > 0.
Mach Traps
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.
machdep Aufrufe
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.
comm-Seite
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.
objc_msgSend
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
Shellcodes
Um zu kompilieren:
Um die Bytes zu extrahieren:
Für neuere macOS:
Last updated