I am attempting to hand-write a Mach-O executable. There are three load commands:
LC_SEGMENT_64
loading__PAGEZERO
LC_SEGMENT_64
loading__TEXT
, with a single__text
sectionLC_UNIXTHREAD
with an appropriately-setrip
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
…
Not 100% sure but you will need
LC_LOAD_DYLINKER
load command to run dyld before your executable, I am pretty certain OSX does not automatically maps to/usr/lib/dyld
if that load command is not available.Do you need
/usr/lib/libSystem.B.dylib
withLC_LOAD_DYLIB
load command? I don't think so but that's a good to have either and does not cost much.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 theXNU kernel
exec_activate_image
function https://github.com/apple/darwin-xnu/blob/0a798f6738bc1db01281fc08ae024145e84df927/bsd/kern/kern_exec.c#L1456Without dyld
Assuming you want a 64-bit macOS executable using only system calls, you need:
LC_SEGMENT_64
__PAGEZERO
(with nonzero size, name can be anything)LC_SEGMENT_64
__TEXT
(name can be anything; must be readable and executable; sections are optional)LC_UNIXTHREAD
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:
LC_SEGMENT_64
__PAGEZERO
(with nonzero size)LC_SEGMENT_64
__TEXT
(name can be anything; must be readable and executable; sections are optional)LC_SEGMENT_64
__LINKEDIT
(must be writable because dyld requires a writable segment, in ald
linked binary the writable segment typically would be__DATA
)LC_DYLD_INFO_ONLY
(specifies where the actualdyld
load commands physically are in the executable, typically they will be found__LINKEDIT
but there's no limitation on this) or interestinglyLC_SYMTAB
instead, which would make the actualdyld
impossible to use withoutLC_DYLD_INFO_ONLY
.LC_DYSYMTAB
(this can be empty)LC_LOAD_DYLINKER
LC_MAIN
orLC_UNIXTHREAD
LC_LOAD_DYLIB
(at least one actual dylib to load forLC_MAIN
to work)LC_UNIXTHREAD
andLC_MAIN
In modern executables (since 10.7 Mountain Lion),
LC_UNIXTHREAD
is replaced byLC_MAIN
, which requiresdyld
— butLC_UNIXTHREAD
is still supported for any executable as of 10.12 Sierra (and it should be in future MacOS versions, because it's utilised bydyld
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 , whereLC_DYLD_INFO_ONLY
pointing to validdyld 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 timedyld_stub_binder
to lazy load address of adyld
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