mach-oxnu

What is required for a Mach-O executable to load?


I am attempting to hand-write a Mach-O executable. There are three load commands:

Every command matches the structs in mach/loader.h and related headers. otool -l lists the information as expected and doesn't report any errors. By all accounts it is a well-formed object file — yet OS X 10.10.5 terminates the task (SIGKILL).

What features of a Mach-O executable are checked before OS X will load it? Where is this information located? Do these features change version-to-version? (The often-cited "OS X ABI Mach-O Reference" is apparently missing.)


Here is a partially annotated hexdump of the binary.

otool sanity check (excerpted):

$ otool -l machtest
machtest:
Load command 0
      cmd LC_SEGMENT_64
  cmdsize 72
  segname __PAGEZERO
…
Load command 1
      cmd LC_SEGMENT_64
  cmdsize 152
  segname __TEXT
…
Section
  sectname __text
   segname __TEXT
…
Load command 2
        cmd LC_UNIXTHREAD
…

Solution

  • Since 10.10.5 Yosemite, the executable file must be at least 4096 bytes long ( PAGE_SIZE ), or it will be killed immediately. The relevant code found by @Siguza in the XNU kernel exec_activate_image function https://github.com/apple/darwin-xnu/blob/0a798f6738bc1db01281fc08ae024145e84df927/bsd/kern/kern_exec.c#L1456

    Without dyld

    Assuming you want a 64-bit macOS executable using only system calls, you need:

    Here is my example for this case.

    With dyld

    You can't do much without dyld though, so if you want to use it the minimal set is:

    Additionally since MacOS Monterey 12.3:

    LC_UNIXTHREAD and LC_MAIN

    In modern executables (since 10.7 Mountain Lion), LC_UNIXTHREAD is replaced by LC_MAIN, which requires dyld — but LC_UNIXTHREAD is still supported for any executable as of 10.12 Sierra (and it should be in future MacOS versions, because it's utilised by dyld executable itself to actually start).

    For dyld to work the extra steps depend on type of binding:
    bind at load is the least effort approach , where LC_DYLD_INFO_ONLY pointing to valid dyld load commands pointing to writable segment will do the trick.
    lazy binding additionally requires extra platform specific code in __TEXT which utilises binded at load time dyld_stub_binder to lazy load address of a dyld loaded function.
    There are other types of dyld binding which I don't cover here.

    Further details can be found here: https://github.com/opensource-apple/dyld/blob/master/src/ImageLoaderMachO.cpp