Use at your own risk. This project is for educational and research purposes only. Running unsigned code on a retail PS4 may void your warranty and violate Sony's terms of service.
A self-contained, portable Mach-O 64-bit segment loader written in C that runs on the PS4's FreeBSD-based kernel. It parses a raw Mach-O image, maps each LC_SEGMENT_64 into anonymous memory, and hands off execution to the binary's entry point — without depending on any Darwin or macOS system libraries.
- Overview
- Repository Structure
- How It Works
- Quick Start
- Building the Payload
- Building the Loader
- Running the Test
- PS4 Integration
- ISO Loader — Installing an OS from Disc Image
- Official Darwin x86 / x64 Resources
- API Reference
- Security Notes
- Troubleshooting
- Further Reading
- Science and Art
- About
The PS4 runs a customised version of FreeBSD 9 on an x86-64 CPU. Apple's macOS uses the Mach-O object format for all executables. Because both platforms share the same CPU architecture (AMD64), a Mach-O binary compiled without any OS-specific dependencies can be mapped and executed on the PS4 by reimplementing the small subset of the Mach-O loader that the kernel normally handles.
This project provides:
| Component | File(s) | Purpose |
|---|---|---|
| Type definitions & public API | macho_loader.h |
Self-contained Mach-O structs; no <mach-o/loader.h> needed |
| Segment loader | macho_loader.c |
Parses the image and maps segments into anonymous memory |
| Execution harness | macho_test.c |
Promotes pages to RX, issues a memory fence, and runs the payload |
| Driver / entry point | main.c |
Reads the payload file and calls test_macho_execution |
| Bare-metal payload | payload.c |
Minimal _start with no libc – cross-compiled to Mach-O |
| ISO 9660 / El Torito loader | iso_loader.h, iso_loader.c |
Parses disc images and extracts boot images or individual files |
ps4/
├── macho_loader.h # Mach-O type definitions and public API declarations
├── macho_loader.c # Core segment loader (load_mach_o_segments)
├── macho_test.c # Execution harness (test_macho_execution)
├── main.c # Driver: reads payload file, calls test_macho_execution
├── payload.c # Bare-metal payload source (cross-compiled separately)
├── iso_loader.h # ISO 9660 / El Torito type definitions and public API
├── iso_loader.c # ISO loader (iso_load_boot_image, iso_find_file)
├── iso_loader_test.c# Self-contained ISO loader test (no disc file needed)
├── Makefile # Build rules for loader, payload, and ISO loader
├── docs/
│ ├── ARCHITECTURE.md # Deep dive into loader internals
│ ├── BUILD.md # Step-by-step build instructions
│ ├── ISO_LOADER.md # ISO 9660 / El Torito loader guide
│ ├── API_REFERENCE.md # Full public API reference
│ ├── PS4_INTEGRATION.md # PS4-specific how-to guide
│ └── TROUBLESHOOTING.md # Common issues & solutions
├── CONTRIBUTING.md
└── LICENSE
The loader performs three sequential passes over the Mach-O image held in memory:
┌─────────────────────────────────────────────────────────┐
│ Pass 1 – Measure │
│ Walk every LC_SEGMENT_64 and record the lowest vmaddr │
│ and the highest (vmaddr + vmsize) to determine the │
│ total virtual-address span. │
└───────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Single mmap() reservation │
│ One contiguous RW region covers the entire span. │
│ An ASLR slide is computed: slide = base − vm_min. │
└───────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Pass 2 – Copy │
│ Each segment's raw bytes are memcpy'd from the file │
│ image into the mapped region at (vmaddr − vm_min). │
└───────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Pass 3 – Locate entry point │
│ LC_MAIN → entry_addr = base + entryoff │
│ LC_UNIXTHREAD → entry_addr = rip_from_thread_state │
│ + slide │
└───────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Caller (macho_test.c) │
│ mprotect(RW→RX) → mfence → call entry_addr() │
└─────────────────────────────────────────────────────────┘
See docs/ARCHITECTURE.md for a full deep-dive.
| Tool | Minimum version | Purpose |
|---|---|---|
| Clang | 10+ | Compile loader & cross-compile payload |
otool (macOS) or objdump (Linux) |
any | Inspect generated Mach-O headers |
| GNU Make (optional) | 3.8+ | Drive the build |
git clone https://github.com/GizzZmo/ps4.git
cd ps4
# Build the loader and test harness
make loaderclang -target x86_64-apple-macos \
-static -nostdlib \
-e __start \
-o bare_metal_test payload.c# macOS
otool -l bare_metal_test
# Linux
objdump -p bare_metal_testLook for LC_SEGMENT_64 with vmaddr = 0x100000000 and LC_MAIN with a valid entryoff.
payload.c is a deliberately dependency-free C file. It must be cross-compiled into a native Mach-O binary so the loader has something to parse.
clang -target x86_64-apple-macos \
-static \ # no dynamic linking
-nostdlib \ # no libSystem or libc
-e __start \ # explicit entry-point symbol (two underscores on Darwin)
-o bare_metal_test payload.cSymbol naming note: Darwin/macOS toolchains automatically prepend an underscore to every C symbol name. The function
_startin C therefore becomes__startin the Mach-O symbol table, which is why-e __start(two underscores) must be passed to the linker.
$ otool -l bare_metal_test | grep -A4 LC_MAIN
cmd LC_MAIN
cmdsize 24
entryoff 0
stacksize 0
The entryoff will be the file offset of _start inside the __TEXT segment.
The loader itself (macho_loader.c + macho_test.c) is standard C99/C11 and compiles on any POSIX host or the PS4 SDK.
clang -std=c11 -Wall -Wextra -O2 \
-o macho_loader_test \
macho_loader.c macho_test.c \
-I.# Replace <PS4_SDK> with the path to your PS4 SDK toolchain
<PS4_SDK>/bin/x86_64-ps4-clang -std=c11 -O2 \
-DSCE_KERNEL_MPROTECT_AVAILABLE \
-o macho_loader.o -c macho_loader.c
<PS4_SDK>/bin/x86_64-ps4-clang -std=c11 -O2 \
-DSCE_KERNEL_MPROTECT_AVAILABLE \
-o macho_test.o -c macho_test.cSetting -DSCE_KERNEL_MPROTECT_AVAILABLE switches the ps4_mprotect macro to call sceKernelMprotect() instead of the POSIX mprotect(2).
Once both binaries are built, embed bare_metal_test as a byte array and pass it to test_macho_execution:
#include "macho_loader.h"
#include <stdio.h>
#include <stdlib.h>
/* Read the Mach-O binary produced by payload.c into a buffer. */
static uint8_t *read_file(const char *path, size_t *out_size) {
FILE *f = fopen(path, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
*out_size = (size_t)ftell(f);
rewind(f);
uint8_t *buf = malloc(*out_size);
fread(buf, 1, *out_size, f);
fclose(f);
return buf;
}
int main(void) {
size_t size;
uint8_t *data = read_file("bare_metal_test", &size);
if (!data) { fputs("Cannot open bare_metal_test\n", stderr); return 1; }
int ok = test_macho_execution(data, size);
printf("Result: %s (expected return value 0x1379)\n",
ok ? "PASS" : "FAIL");
free(data);
return ok ? 0 : 1;
}Expected output:
Result: PASS (expected return value 0x1379)
0x1379 = 0x1337 + 0x42 — the value returned by _start() in payload.c.
See docs/PS4_INTEGRATION.md for the complete guide. A brief summary:
- Obtain a kernel exploit that allows unsigned code execution (jailbreak). This loader does not provide one.
- Compile
macho_loader.candmacho_test.cwith the PS4 SDK toolchain and-DSCE_KERNEL_MPROTECT_AVAILABLE. - Embed the Mach-O payload binary inside your existing PS4 payload as a byte array.
- Call
test_macho_execution(payload_bytes, payload_size)from your payload's entry point. - Verify the return value;
1means the Mach-O image was successfully mapped and executed.
mmap() on PS4 returns pages that are RW only. Before jumping to the entry point you must upgrade the protection:
// PS4 SDK equivalent of mprotect(2)
sceKernelMprotect(mmap_base, total_size, VM_PROT_READ | VM_PROT_EXEC);The ps4_mprotect macro in macho_test.c handles this automatically when compiled with -DSCE_KERNEL_MPROTECT_AVAILABLE.
The ISO loader (iso_loader.h / iso_loader.c) provides the foundation for
installing or booting a third-party operating system on the PS4 directly from a
standard .iso disc image (any Linux distribution, Darwin installer, etc.).
See docs/ISO_LOADER.md for the complete guide. A brief summary:
| Function | Purpose |
|---|---|
iso_load_boot_image(iso_data, iso_size, &out) |
Parse the El Torito boot catalog and extract the default boot image into a malloc'd buffer |
iso_find_file(iso_data, iso_size, "/path/to/file", &size) |
Traverse the ISO 9660 directory tree and return a direct pointer to any file (kernel, initrd, config) |
iso_free_boot_image(&img) |
Release memory allocated by iso_load_boot_image |
make iso_loader # compile iso_loader_test
./iso_loader_test # expected: ISO loader test: PASS
make test # runs both the Mach-O integration test and ISO loader test#include "iso_loader.h"
#include "macho_loader.h"
/* 1. Read or embed the ISO image */
extern const uint8_t iso_data[];
extern const size_t iso_size;
/* 2. Extract the El Torito boot image */
iso_boot_image_t boot_img;
if (iso_load_boot_image(iso_data, iso_size, &boot_img) != 0) {
notify("ISO parse failed");
return;
}
/* 3. Inspect the boot image type before use.
* El Torito payloads are typically x86 BIOS boot sectors (platform_id =
* ISO_ELTORITO_PLATFORM_X86) or UEFI FAT/PE images (PLATFORM_EFI), NOT
* Mach-O binaries. Do not pass arbitrary El Torito payloads to the
* Mach-O loader. */
if (boot_img.platform_id == ISO_ELTORITO_PLATFORM_X86) {
/* BIOS boot sector */
} else if (boot_img.platform_id == ISO_ELTORITO_PLATFORM_EFI) {
/* UEFI FAT/PE image */
}
iso_free_boot_image(&boot_img);
/* 3b. Linux ISO — find kernel and initrd directly */
size_t kernel_size, initrd_size;
const uint8_t *kernel = iso_find_file(iso_data, iso_size,
"/boot/vmlinuz", &kernel_size);
const uint8_t *initrd = iso_find_file(iso_data, iso_size,
"/boot/initrd.img", &initrd_size);
/* Then set up struct boot_params and jump to kernel_base + 0x200 */To cross-compile Mach-O x86 and x86_64 binaries targeting Darwin / macOS:
| Resource | URL |
|---|---|
| Xcode (full IDE + SDK) | https://developer.apple.com/download/applications/ |
| Xcode Command Line Tools (SDK only) | https://developer.apple.com/download/all/?q=command+line+tools |
| Apple Open Source (XNU / Darwin kernel) | https://opensource.apple.com/ |
| XNU kernel source — GitHub mirror | https://github.com/apple-oss-distributions/xnu |
| macOS release history and downloads | https://support.apple.com/en-us/100100 |
Intel / x86_64 note: macOS Sonoma (14) is the last release to support Intel Macs. macOS Tahoe 26.4.1 is the latest release and requires Apple Silicon. The
-target x86_64-apple-macosClang flag works from both Intel and Apple Silicon hosts and targets the same x86_64 Mach-O ABI.
| Resource | URL |
|---|---|
| osxcross — macOS cross-toolchain for Linux | https://github.com/tpoechtrager/osxcross |
| LLVM / Clang releases | https://releases.llvm.org/ |
| Apple Open Source tarballs (SDK headers) | https://opensource.apple.com/tarballs/ |
# Quick osxcross setup
git clone https://github.com/tpoechtrager/osxcross.git
cd osxcross
# Place a macOS SDK tarball in tarballs/ — see the osxcross README
UNATTENDED=1 ./build.sh
export PATH="$PATH:$(pwd)/target/bin"
# Cross-compile Mach-O x86_64 on Linux:
o64-clang -target x86_64-apple-macos -static -nostdlib -e __start \
-o bare_metal_test payload.cSee docs/API_REFERENCE.md for the full reference. Key functions:
void *load_mach_o_segments(const uint8_t *macho_data, size_t size,
void **out_map_base, size_t *out_map_size);| Parameter | Description |
|---|---|
macho_data |
Pointer to the raw Mach-O image in memory |
size |
Byte length of the buffer |
out_map_base |
Optional — receives the mmap reservation base (for munmap/mprotect) |
out_map_size |
Optional — receives the mmap reservation size (for munmap/mprotect) |
Returns a pointer to the entry point on success, NULL on any error (bad magic, missing entry point, allocation failure, malformed load commands).
Caller responsibilities:
- Call
mprotect(orsceKernelMprotect) without_map_base/out_map_sizeto set the mapped pages toRXbefore executing the returned pointer. - Free the mapping with
munmap(out_map_base, out_map_size)when finished.
int test_macho_execution(const uint8_t *macho_data, size_t size);Convenience wrapper that calls load_mach_o_segments, promotes pages to RX, issues mfence, executes the payload, and validates the result against 0x1379.
Returns 1 on success (payload returned 0x1379), 0 on any failure.
- NX / DEP bypass: Mapped pages are initially
RW. The explicitmprotect(RX)call removes write permission before execution. This is correct and required; do not map pages asRWXsimultaneously. - Buffer validation: Both passes in
load_mach_o_segmentsvalidate that every load command and segment fits within the supplied buffer before accessing it. - ASLR: The loader uses
MAP_FIXED_NOREPLACEwhen available (Linux ≥ 4.17), falling back to a kernel-chosen address. The ASLR slide is correctly propagated toLC_UNIXTHREADentry points. - No syscall translation: This PoC only handles the binary format. Any payload that makes Darwin-specific syscalls will crash on PS4 until a Darwin→FreeBSD syscall shim is added.
| Symptom | Likely cause | Fix |
|---|---|---|
load_mach_o_segments returns NULL |
Wrong magic bytes / not a 64-bit Mach-O | Confirm file with file bare_metal_test |
| Segmentation fault on execution | Pages not promoted to RX | Ensure mprotect/sceKernelMprotect succeeds before the jump |
Result is not 0x1379 |
Symbol name mismatch | Verify with nm bare_metal_test; entry must be __start |
mmap fails on PS4 |
Insufficient RW region | Ensure the kernel exploit has unlocked user-land memory mapping |
| Clang cross-compile fails on Linux | Missing macOS SDK sysroot | Use osxcross or a macOS VM; see docs/BUILD.md |
See docs/TROUBLESHOOTING.md for an extended list with stack traces and diagnostic commands.
docs/ARCHITECTURE.md— Mach-O format primer, loader design, ASLR handlingdocs/BUILD.md— Detailed build matrix (macOS, Linux, PS4 SDK)docs/ISO_LOADER.md— ISO 9660 / El Torito loader guide and PS4 OS installationdocs/API_REFERENCE.md— Full API documentation with error codesdocs/PS4_INTEGRATION.md— End-to-end PS4 integration walkthroughdocs/TROUBLESHOOTING.md— Diagnostics and common pitfalls- Apple's Mach-O Runtime Architecture — Official format specification
- Apple Open Source (Darwin / XNU) — Official Darwin kernel and toolchain source
- XNU kernel source — GitHub mirror — Latest Darwin x86/x64 kernel code
- PS4 Developer Wiki — Community PS4 internals reference
See LICENSE for terms.
This project sits at the intersection of low-level systems engineering and the craft of understanding how hardware and software interact at a fundamental level.
- Binary format research — dissecting the Mach-O object format to understand how modern operating systems load and execute code, from ELF on Linux to Mach-O on macOS/Darwin.
- Cross-architecture portability — exploring how the same AMD64 instruction stream can be executed on wildly different OS kernels (FreeBSD/PS4 vs. macOS) given the right loader shim.
- Memory management — studying virtual memory mapping, page permissions (
RW → RX), ASLR, and the kernel interfaces (mmap,mprotect,sceKernelMprotect) that control them. - Reverse engineering — using tools such as
otool,objdump, andnmto inspect and validate binary artefacts — a core skill in security research and embedded systems.
- Minimal C as craft — writing dependency-free C that compiles cleanly with
-nostdlibis a discipline: every byte must be intentional, every pointer validated. - Systems as canvas — hacking a game console to run foreign binaries is, in its own way, a creative act: turning a closed device into an open platform where new ideas can run.
- Documentation as storytelling — the ASCII flowcharts, tables, and structured prose in this project reflect the belief that clear communication is as important as correct code.
Jon-Arve Constantine is a developer and researcher with a passion for low-level systems programming, security research, and open-source exploration.
- 🐙 GitHub: github.com/GizzZmo
PS4 Mach-O Loader is a proof-of-concept that demonstrates how a Mach-O 64-bit binary — normally only executable on macOS/Darwin — can be mapped and run on the PS4's FreeBSD-based kernel. It is intended purely for educational and research purposes, showing how binary loaders work at the OS level and how CPU-architecture compatibility can be leveraged across different operating systems.
Key goals:
- Understand the Mach-O load-command pipeline end-to-end.
- Produce a minimal, auditable loader with no external dependencies.
- Serve as a foundation for further syscall-shim research.
Explore more work by Jon-Arve Constantine at github.com/GizzZmo, including projects spanning systems programming, security tooling, and open-source experiments.