Chapters

Hide chapters

Advanced Apple Debugging & Reverse Engineering

Third Edition · iOS 12 · Swift 4.2 · Xcode 10

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section III: Low Level

Section 3: 7 chapters
Show chapters Hide chapters

Section IV: Custom LLDB Commands

Section 4: 8 chapters
Show chapters Hide chapters

18. Hello, Mach-O
Written by Derek Selander

Mach-O is the file format used for a compiled program running on any of your Apple operating systems. Knowledge of the format is important for both debugging and reverse engineering, since the layout Mach-O defines is applicable to how the executable is stored on disk as well as how the executable is loaded into memory.

Knowing which area of memory an instruction is referencing is useful on the reverse engineering side, but there are a number of useful hidden treasures on the debugging front when exploring Mach-O. For example:

  • You can instrospect an external function call at runtime.
  • You can quickly find the reference to a singleton’s memory address without having to trip a breakpoint.
  • You can inspect and modify variables in your own app or other frameworks
  • You can perform security audits and make sure no internal, secret messages are being sent out into production in the form of strings or methods.

This chapter introduces the concepts of Mach-O, while the next chapter, Mach-O Fun will show the amusing things that are possible with this knowledge. Make sure you have that caffeine on board for this chapter since the theory comes first, followed by the fun in the following chapter.

Terminology

Before diving into the weeds with all the different C structs you’re about to view, it would be best to take a high level, birds-eye view of the Mach-O layout.

This is the layout of every compiled executable; every main program, every framework, every kernel extension, everything that’s compiled on an Apple platform.

At the start of every compiled Apple program is the Mach-O header that gives information about the CPU this program can run on, the type of executable it is (A framework? A standalone program?) as well as how many load commands immediately follow it.

Load commands are instructions on how to load the program and are made up of C structs, which vary in size depending on the type of load command.

Some of the load commands provide instructions about how to load segments. Think of segments as areas of memory that have a specific type of memory protection. For example, executable code should only have read and execute permissions; it doesn’t need write permissions.

Other parts of the program, such as global variables or singletons, need read and write permissions, but not executable permissions. This means that executable code and the address to global variables will live in separate segments.

Segments can have 0 or more subcomponents called sections. These are more finely-grained areas bound by the same memory protections given by their parent segment.

Take another look at the above diagram. Segment Command 1, points to an offset in the executable that contains four section commands, while Segment Command 2 points to an offset that contains 0 section commands. Finally, Segment Command 3 doesn’t point to any offset in the executable.

It’s these sections that can be of profound interest to developers and reverse engineerers since they each serve a unique purpose to the program. For example, there’s a specific section to store hard-coded UTF-8 strings, there’s a specific section to store references to statically defined variables and so on.

The ultimate goal of these two Mach-O chapters is to show you some interesting load commands in this chapter, and reveal some interesting sections in the next chapter.

In this chapter, you’ll be seeing a lot of references to system headers. If you see something like mach-o/stab.h, you can view it via the Open Quickly menu in Xcode by pressing ⌘ + Shift + O (the default), then typing in /usr/include/mach-o/stab.h.

I’d recommend adding a /usr/include/ to the search query since Xcode isn’t all that smart at times.

If you want to view this header without Xcode, then the physical location will be at:

${PATH_TO_XCODE}/Contents/Developer/Platforms/${SYSTEM_PLATFORM}.platform/Developer/SDKs/${SYSTEM_PLATFORM}.sdk/usr/include/mach-o/stab.h 

Where ${SYSTEM_PLATFORM} can be MacOSX, iPhoneOS, iPhoneSimulator, WatchOS, etc.

Now you’ve gotten a birds-eye overview, it’s time to drop down into the weeds and view all the lovely C structs.

The Mach-O header

At the beginning of every compiled Apple executable is a special struct that indicates if it’s a Mach-O executable. This struct can be found in mach-o/loader.h.

Remember the name of this header file, as it will be referenced quite a bit in this chapter.

There are two variants to this struct: one for 32-bit operating systems (mach_header), and one for 64-bit operating systems (mach_header_64). This chapter will talk about 64-bit systems by default, unless otherwise stated.

Let’s take a look at the layout of the struct mach_header_64.

struct mach_header_64 {
  uint32_t  magic;    /* mach magic number identifier */
  cpu_type_t  cputype;  /* cpu specifier */
  cpu_subtype_t cpusubtype; /* machine specifier */
  uint32_t  filetype; /* type of file */
  uint32_t  ncmds;    /* number of load commands */
  uint32_t  sizeofcmds; /* the size of all the load commands */
  uint32_t  flags;    /* flags */
  uint32_t  reserved; /* reserved */
};

The first member, magic, is a hard-coded 32-bit unsigned integer that indicates that this is the beginning of a Mach-O header.

What is the value of this magic number? A little further down in the mach-o/loader.h header, you’ll find the following:

/* Constant for the magic field of the mach_header_64 (64-bit architectures) */
#define MH_MAGIC_64 0xfeedfacf /*the 64-bit mach magic number*/
#define MH_CIGAM_64 0xcffaedfe /*NXSwapInt(MH_MAGIC_64)*/

This means that every 64-bit Mach-O executable will begin with either 0xfeedfacf, or 0xcffaedfe if the byte ordering is swapped. On 32-bit systems, the magic value is 0xfeedface, or 0xcefaedfe if byte-swapped.

It’s this value that will let you quickly determine if the file is a Mach-O executable as well as if it’s been compiled for a 32-bit or 64-bit architecture.

After the magic number are cputype and cpusubtype, which indicates on which type of cpu this Mach-O executable is allowed to run.

filetype is useful to know which type of executable you’re dealing with.

Again, consulting mach-o/loader.h shows you the following definitions…

#define MH_OBJECT 0x1   /* relocatable object file */
#define MH_EXECUTE  0x2 /* demand paged executable file */
#define MH_FVMLIB 0x3   /* fixed VM shared library file */
#define MH_CORE   0x4   /* core file */
... // there’s way more below but ommiting for brevity...

So for a main executable (i.e. not a framework), the filetype will be MH_EXECUTE.

After the filetype, the next most interesting aspects of the header are ncmds and sizeofcmds. The load commands indicate the attributes and how the executable is loaded into memory.

Time to take break from theory and see this in the wild by examining the raw bytes of an executable’s Mach-O header in Terminal.

Mach-O header in grep

Open up a Terminal window. I’ll pick on the grep executable command, but you can pick on any Terminal command that suits your interests. Type the following:

xxd -l 32 $(which grep)

This command says to dump just the first 32 raw bytes of the fullpath to the location of the grep executable. Why 32 bytes? In the struct mach_header_64 declaration, there are 8 variables, each 4 bytes long.

You’ll get something similar to the following:

00000000: cffa edfe 0700 0001 0300 0080 0200 0000  ................
00000010: 1300 0000 4007 0000 8500 2000 0000 0000  ....@..... .....

Now is a good time to remind yourself that x86_64 bit Intel systems use a little-endian architecture. That means that the bytes are reversed.

Note: Even though the x86_64 Intel architecture is little-endian, Apple can store Mach-O information in big-endian or little-endian format, which is partly due to historical reasons dating back to the PPC architecture.

iOS doesn’t do this, so every iOS file’s Mach-O header will be little-endian on disk and in memory.

In contrast, the Mach-O header ordering on disk can be found in either format on macOS, but will be little-endian in memory.

Later in this section, you’ll look at macOS’s CoreFoundation module, whose Mach-O header is stored in big-endian format. Standards, eh?

Take a closer look at those first 4 bytes from the xxd output.

cffa edfe 

This can be split out into individual bytes…

cf fa ed fe

Then reversed, byte-wise…

fe ed fa cf

And now, the MH_MAGIC_64, a.k.a. the 0xfeedfacf magic variable, should be evident, indicating this was compiled for a 64-bit system.

Fortunately, the xxd Terminal command has a special option for little-endian architectures: the -e option. Add the -e option to your previous terminal command.

xxd -e -l 32 $(which grep)

You’ll get something similar to the following:

00000000: feedfacf 01000007 80000003 00000002  ................
00000010: 00000013 00000740 00200085 00000000  ....@..... .....

Let’s put all of those values into the struct mach_header_64:

struct mach_header_64 {
  uint32_t      magic      = 0xfeedfacf
  cpu_type_t    cputype    = 0x01000007
  cpu_subtype_t cpusubtype = 0x80000003
  uint32_t      filetype   = 0x00000002
  uint32_t      ncmds      = 0x00000013
  uint32_t      sizeofcmds = 0x00000740
  uint32_t      flags      = 0x00200085
  uint32_t      reserved   = 0x00000000
};

Here you can see the magic number of 0xfeedfacf for the first value. That’s a little easier than doing the reversing of the bytes in your head!

After the 0xfeedfacf, there’s a 0x01000007. To figure this value out, you must consult mach/machine.h, which contains the following values:

#define CPU_ARCH_ABI64    0x01000000  /* 64 bit ABI */
...
#define CPU_TYPE_X86    ((cpu_type_t) 7)

The machine type is CPU_ARCH_ABI64 ORed together with CPU_TYPE_X86 producing 0x01000007 in hex (or 16777223 in decimal).

Note: Depending on your computer model, and the version of grep, you might receive some different output but the format of the Mach-O will remain the same. Use this as a reference to determine your own unique values.

Likewise, the cpusubtype value of 0x80000003 can be determined from the same header file with CPU_SUBTYPE_LIB64 and CPU_SUBTYPE_X86_64_ALL ORed together. filetype has a value of 0x00000002, or more precisely, MH_EXECUTE.

There are 19 load commands (0x00000013 in hex, whose size is 0x00000740). The flags value of 0x00200085 contains a series of options ORed together, but I’ll let you jump into mach-o/loader.h to figure those out on your own.

If you need a specific homework task, find the significance of the 0x00200000 value in the flags variable.

And finally, there is the reserved value, which is just a bunch of boring zeros, and means nothing here!

The fat header

Some executables are actually a group of one or more executables “glued” together. For example, many apps compile both a 32-bit and 64-bit executable and place them into a “fat” executable. This “gluing together” of multiple executables is indicated by a fat header, which also has a unique magic value differentiating it from a Mach-O header.

Immediately following the fat header are structs, which indicate the CPU type and the offset into the file to where the fat header is stored.

Looking at mach-o/fat.h gives the following struct:

#define FAT_MAGIC 0xcafebabe
#define FAT_CIGAM 0xbebafeca  /* NXSwapLong(FAT_MAGIC) */

struct fat_header {
  uint32_t  magic;    /* FAT_MAGIC or FAT_MAGIC_64 */
  uint32_t  nfat_arch;  /* number of structs that follow */
};

...
#define FAT_MAGIC_64  0xcafebabf
#define FAT_CIGAM_64  0xbfbafeca  /* NXSwapLong(FAT_MAGIC_64) */

Although there’s a 64-bit equivalent to the fat header, the 32-bit is still widely used in 64-bit systems. The 64-bit fat header is really only used if the offset of the executable slices is greater than 4MB. This is unlike the Mach-O 64-bit variant header you saw in the previous section, which is used only in 64-bit systems.

The fat header contains a number of fat architecture structs in the value nfat_arch that immediately follow the fat header.

Here’s the 64-bit version of the fat architecture:

struct fat_arch_64 {
  cpu_type_t  cputype;  /* cpu specifier (int) */
  cpu_subtype_t cpusubtype; /* machine specifier (int) */
  uint64_t  offset;   /* file offset to this object file */
  uint64_t  size;   /* size of this object file */
  uint32_t  align;    /* alignment as a power of 2 */
  uint32_t  reserved; /* reserved */
};

And here’s the 32-bit version of the fat architecture:

struct fat_arch {
  cpu_type_t  cputype;  /* cpu specifier (int) */
  cpu_subtype_t cpusubtype; /* machine specifier (int) */
  uint32_t  offset;   /* file offset to this object file */
  uint32_t  size;   /* size of this object file */
  uint32_t  align;    /* alignment as a power of 2 */
};

The magic value in the fat header will indicate which of these 32-bit or 64-bit structs to use.

Want to see a real life example of an executable with a fat header? Check out macOS’s CoreFoundation framework. In Terminal, type this:

file /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation 

You’ll see the following:

/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation: Mach-O universal binary with 3 architectures: [x86_64:Mach-O 64-bit dynamically linked shared library x86_64] [x86_64h]
/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (for architecture x86_64): Mach-O 64-bit dynamically linked shared library x86_64
/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (for architecture i386): Mach-O dynamically linked shared library i386
/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (for architecture x86_64h):  Mach-O 64-bit dynamically linked shared library x86_64h

This says the CoreFoundation consists of three architectures sliced and glued together: x86_64, i386, x86_64h. What’s up with the x86_64h architecture? It stands for Haswell, a x86_64 variant introduced to Macbook Pros in October 2013. On your own time, you can see which architecture is loaded by using LLDB on a program that loads the Core Foundation module.

For example, the following would work when debugging a macOS app that linked to CoreFoundation. Open a Terminal window and type the following:

lldb $(which plutil)

This will open an LLDB session for the plutil application, which is a little tool shipped with macOS for manipulating property lists. It just so happens to link Core Foundation, so it’s a good one to use for this example.

You’ll need to run the application once just so that it actually loads the libraries. Type run like so:

(lldb) run
Process 946 launched: ’/usr/bin/plutil’ (x86_64)
No files specified.
plutil: [command_option] [other_options] file...
... etc ...

Then inside the LLDB session, type the following:

(lldb) image list -h CoreFoundation
[  0] 0x00007fff33cf6000

This will dump the load address of the CoreFoundation module. After you’ve obtained the load address, dump the memory containing the Mach-O header.

(lldb) x/8wx 0x00007fff33cf6000
0x7fff33cf6000: 0xfeedfacf 0x01000007 0x00000008 0x00000006
0x7fff33cf6010: 0x00000013 0x00001100 0xc2100085 0x00000000

On my computer, cpusubtype contains the value 0x00000008, which equates to the following in mach/machine.h:

#define CPU_SUBTYPE_X86_64_H    ((cpu_subtype_t)8)  /* Haswell feature subset */

So I can tell that the Haswell, x86_64h slice of CoreFoundation was loaded into my process.

Jumping back to the on-disk representation of CoreFoundation, dump the raw bytes. Exit out of LLDB and type the following:

xxd -l 68 -e /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation

This will produce output similar to:

00000000: bebafeca 03000000 07000001 03000000  ................
00000010: 00100000 30767400 0c000000 07000000  .....tv0........
00000020: 03000000 00907400 e0ca6700 0c000000  .....t...g......
00000030: 07000001 08000000 0060dc00 d0e67400  ..........`..t..
00000040: 0c000000                             ....

0xbebafeca or FAT_CIGAM is a 32-bit fat header in byte swapped (big-endian) format. This means that the -e is not necessary. Why is 68 bytes used for a length? Let’s do the math…

  • There’s a struct fat_header right at the beginning containing two, 4-byte members called magic and nfat_arch. The nfat_arch has a value of 0x03000000, but since you know it’s byte-swapped, the actual value is 0x00000003. This brings the total count to 8 bytes so far from this header.

  • Immediately following the fat_header are three struct fat_archs (because nfat_arch is 3). The fat_arch is the 32-bit equivalent which contains 5, 4-byte members. That means there’s an additional 60 bytes (20-bytes × 3) of interest, which brings the total byte count to 68.

Augment the above Terminal command by replacing the -e argument with the byte size argument (-g), saying to display output in 4-byte groupings.

xxd -l 68 -g 4 /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation

Now the raw fat header data should be more viewable.

00000000: cafebabe 00000003 01000007 00000003  ................
00000010: 00001000 00747630 0000000c 00000007  .....tv0........ 
00000020: 00000003 00749000 0067cae0 0000000c  .....t...g......
00000030: 01000007 00000008 00dc6000 0074e6d0  ..........`..t..
00000040: 0000000c                             ....

Note: If the fat header was the 64-bit variant, the -g 4 option wouldn’t have worked since there are a couple of 8-byte variables mixed with 4-byte ones in struct fat_arch_64.

The first two values — 0xcafebabe and 0x00000003 — are the struct fat_header, while the remaining bytes will belong to one of the three struct fat_archs. Examining the first struct fat_arch, we can see it’s for x86_64 due to the cputype 0x01000007 and cpusubtype 0x00000003 that you saw previously. The offset to the start of the x86_64 slice is 0x00001000 (4096) and whose size is 0x00747630.

To prove that the x86_64 slice is at offset 4096 from the start of the file, dump the x86_64 header using xxd’s -s option.

xxd -l 32 -e -s 4096 /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation 

As you can guess, the -s option specifies an offset to start at. You’ll see the x86_64 slice’s Mach-O header.

00001000: feedfacf 01000007 00000003 00000006  ................
00001010: 00000015 00001120 02100085 00000000  .... ...........

Now that the headers are discussed, time to jump into the load commands.

The load commands

Immediately following the Mach-O header are the load commands providing instructions on how an executable should be loaded into memory, as well as other miscellaneous details. This is where it gets interesting. Each load command consists of a series of structs, each varying in struct size and arguments.

Fortunately, for each Load Command struct, the first two variables are always consistent, the cmd and the cmdsize.

cmd will indicate the type of load command and the cmdsize will give you the size of the struct. This lets you iterate over the load commands and then jump by the appropriate cmdsize.

The Mach-O authors anticipated this situation and provided a generic load command struct named struct load_command.

struct load_command {
  uint32_t cmd;   /* type of load command */
  uint32_t cmdsize; /* total size of command in bytes */
};

This lets you start with each load command as this generic struct load_command. Once you know the cmd value, you can cast the memory address into the appropriate struct.

So what are the values that cmd can have? Again, we put our faith in mach-o/loader.h.

#define LC_SEGMENT_64 0x19  /*64-bit segment of this file to be mapped*/
#define LC_ROUTINES_64  0x1a  /* 64-bit image routines */
#define LC_UUID   0x1b  /* the uuid */

If you see a constant that begins with LC, then you know that’s a load command. There are 64-bit and 32-bit equivalents to load commands, so make sure you use the appropriate one. 64-bit load commands will end in a “_64” in the name. That being said, 64-bit systems can still use 32-bit load commands. For example, the LC_UUID load command doesn’t contain the _64 in the name but is included in all executables.

LC_UUID is one of the simpler load commands, so it’s a great example to start out with. The LC_UUID provides the Universal Unique Identifier to identify a specific version of an executable. This load command doesn’t provide any specific segment information, as it’s all contained in the LC_UUID struct.

In fact, the load command struct for the LC_UUID load command is the struct uuid_command found in mach-o/loader.h:

/*
 * The uuid load command contains a single 128-bit unique random number that
 * identifies an object produced by the static link editor.
 */
struct uuid_command {
    uint32_t  cmd;    /* LC_UUID */
    uint32_t  cmdsize;  /* sizeof(struct uuid_command) */
    uint8_t uuid[16]; /* the 128-bit uuid */
};

Going back to grep, you can view grep’s UUID using the otool command with the -l (load command) option.

otool -l $(which grep) | grep LC_UUID -A2

This will dump the two lines following any hits that contain the phrase LC_UUID.

     cmd LC_UUID
 cmdsize 24
    uuid 3B067B3F-4F1F-39A3-A4B9-CFDD595F9289

otool has translated the cmd from 0x1b to LC_UUID, displays the cmdsize to sizeof(struct uuid_command) (aka 24 bytes) and has displayed the UUID value in a pretty format. If you’re using the same macOS version as me, then you’ll have the same UUID!

Segments

The LC_UUID is a simple load command since it’s self-contained and doesn’t provide offsets into the executable’s segments/sections. It’s now time to turn your attention to segments.

A segment is a grouping of memory that has specific permissions. A segment can have 0 or more subcomponents named sections.

Before going into the load command structs that provide instructions for segments, let’s talk about some segments that are typically found in a program.

  • The __PAGEZERO segment is a section in memory that is essentially a “no man’s land.” This segment contains 0 sections. This memory region doesn’t have read, write, or execute permissions and occupies the lower 32-bits in a 64-bit process. This is useful in case the developer screws up a pointer, or dereferences NULL; if this happens, the program will crash since there’s no read permissions on that memory region (bits 0 to 2^32). Only the main executable (i.e. not a framework) will contain a __PAGEZERO load command.

  • The __TEXT segment stores readable and executable memory. This is where the application’s code lives. If you want to store something that shouldn’t be changed around in memory (like executable code or hard-coded strings), then this is the segment to put content in. Typically the __TEXT segment will have multiple sections for storing various immutable data.

  • The __DATA segment stores readable and writable memory. This is where the majority of Objective-C data goes (since the language is dynamic and can change at runtime) as well as mutable variables in memory. Typically the __DATA segment will have multiple sections for storing various mutable data.

  • The __LINKEDIT Segment is a grab-bag of content that only has readable permissions. This segment stores the symbol table, the entitlements file (if not on the Simulator), the codesigning information and other essential information that enables a program to function. Even though this segment has lots of important data packed inside, it has no sections.

Let’s hammer this information in by looking at a real-life example. Use LLDB and attach to any process. Yes, any process! I’ll choose the Simulator’s SpringBoard for the example.

After starting up Simulator, type the following:

lldb -n SpringBoard

Once attached, type the following:

(lldb) image dump sections SpringBoard

This will dump the sections (and segments) for the SpringBoard module.

Sections for ’/Applications/Xcode-beta.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/CoreServices/SpringBoard.app/SpringBoard’ (x86_64):
  SectID     Type             Load Address                             Perm File Off.  File Size  Flags      Section Name
  ---------- ---------------- ---------------------------------------  ---- ---------- ---------- ---------- ----------------------------
  0x00000100 container        [0x0000000000000000-0x0000000100000000)* ---  0x00000000 0x00000000 0x00000000 SpringBoard.__PAGEZERO
  0x00000200 container        [0x000000010c0da000-0x000000010c8b4000)  r-x  0x00000000 0x007da000 0x00000000 SpringBoard.__TEXT
  0x00000001 code             [0x000000010c0de69c-0x000000010c70d663)  r-x  0x0000469c 0x0062efc7 0x80000400 SpringBoard.__TEXT.__text
... etc ...

Again, this is the in memory breakdown of the different Segments and Sections. If you wanted to see the on disk Mach-O layout instructions, you can use the following command:

(lldb) image dump objfile SpringBoard

This will give you a fair bit of information since it spits out the Mach-O load commands as well as all the symbols found in the module. If you scroll to the top, you’ll find all the load commands.

It’s important to note the difference between the on-disk location of the segment and sections, versus the actual memory location of the segment and sections, since they will have different values once loaded into memory.

Note: And, as usual, I’ve written a LLDB command that displays the output in a much nicer format and has more advanced options for getting information out of specific sections in an executable. You can find it here: https://github.com/DerekSelander/LLDB/blob/master/lldb_commands/section.py

Programmatically finding segments and sections

For the demo part of this chapter, you’ll build a macOS executable that iterates through the loaded modules and prints all the segments and sections found in each module.

Open Xcode, create a new project, select macOS then Command Line Tool, and name this program MachOSegments. Make sure the Swift language is selected.

Open main.swift and replace its contents with the following:

import Foundation
import MachO // 1

for i in 0..<_dyld_image_count() { // 2
  let imagePath =
    String(validatingUTF8: _dyld_get_image_name(i))! // 3 
  let imageName = (imagePath as NSString).lastPathComponent 
  let header = _dyld_get_image_header(i)! // 4
  print("\(i) \(imageName) \(header)")
}

CFRunLoopRun() // 5

Breaking this down:

  1. Although Foundation will indirectly import the MachO module, you are explicitly importing the MachO module just to be safe and for code clarity. You’ll be using several of the structs found in mach-o/loader.h in a second.

  2. The _dyld_image_count function will return the total count of all the loaded modules in the process. You’ll use this to iterate over all the modules in a for loop.

  3. The _dyld_get_image_name function will return the full path of the image.

  4. The _dyld_get_image_header will return the load address of the Mach-O header (mach_header or mach_header_64) for that current module.

  5. The CFRunLoopRun will prevent the app from exiting. This is ideal, because I’ll have you inspect the process with LLDB after the output is done.

Build and run the program. You’ll see a list of modules and their load addresses spit out to the console. These load addresses are the location to where that particular Mach-O header resides in memory for that module. This is almost the exact same as doing a image list -b -h in LLDB! If you’re curious and want one of these values to take a peek the Mach-O header, copy one of the values and use LLDB to dump the memory.

For example, in my output I see the following:

8 CoreFoundation 0x00007fff33cf6000

You can view the raw bytes of CoreFoundations Mach-O Header by pausing execution and typing the following in LLDB:

(lldb) x/8wx 0x00007fff33cf6000

And then you’ll see something similar to the following:

0x7fff33cf6000: 0xfeedfacf 0x01000007 0x00000008 0x00000006
0x7fff33cf6010: 0x00000013 0x00001100 0xc2100085 0x00000000

Now that you have the basic output in the MachOSegments program, add the following code to the end of the for loop:

var curLoadCommandIterator = Int(bitPattern: header) + 
  MemoryLayout<mach_header_64>.size // 1
for _ in 0..<header.pointee.ncmds {
  let loadCommand = 
    UnsafePointer<load_command>(
      bitPattern: curLoadCommandIterator)!.pointee // 2

  if loadCommand.cmd == LC_SEGMENT_64 {
    let segmentCommand = 
      UnsafePointer<segment_command_64>(
        bitPattern: curLoadCommandIterator)!.pointee // 3

    print("\t\(segmentCommand.segname)")
  }

  curLoadCommandIterator = 
    curLoadCommandIterator + Int(loadCommand.cmdsize) // 4
}

This is where the ugliness of Swift and C interopability really starts to rear its ugly head. Again with the numbers breakdown:

  1. Load commands start immediately after the Mach-O header, so the header address is added to the size of the load address of the mach_header_64 to determine where the load commands start. A good program would check if it’s running a 32-bit mode by determining the magic value, but it’s fun to walk on the wild side occassionally…

  2. Using Swift’s UnsafePointer to cast the load command to the “generic” load_command struct that you saw earlier. If this struct contains the correct cmd value, you’ll cast this memory address to the appropriate segment_command_64 struct.

  3. Here you know that the load_command struct should actually be a segment_command_64 struct, so we’re using Swift’s UnsafePointer object again.

  4. At the end of each loop, we need to increment the curLoadCommandIterator variable by the size of the current loadCommand, which is determined by its cmdsize variable.

Note: How did I know to cast the segment_command_64 struct when I saw the value LC_SEGMENT_64? In the mach-o/loader.h header, search for all references to LC_SEGMENT_64. There’s the declaration that defines LC_SEGMENT_64 and then there’s the segment_command_64 which states its cmd is LC_SEGMENT_64.

Finding all references to the load command will give you the appropriate C struct.

Build and run.

Upon execution, you’ll get some rather ugly output like the one truncated one below.

0 MachOPOC 0x0000000100000000
  (95, 95, 80, 65, 71, 69, 90, 69, 82, 79, 0, 0, 0, 0, 0, 0)
  (95, 95, 84, 69, 88, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
  (95, 95, 68, 65, 84, 65, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
  (95, 95, 76, 73, 78, 75, 69, 68, 73, 84, 0, 0, 0, 0, 0, 0)

This is because Swift is really terrible when working with C. segmentCommand.segname is declared as a Swift tuple of Int8s. This means you get to build a helper function to convert these values to an actual readable Swift String.

Jump to the top part main.swift and declare the following function.

func convertIntTupleToString(name : Any) -> String {
  var returnString = ""
  let mirror = Mirror(reflecting: name)
  for child in mirror.children {
    guard let val = child.value as? Int8,
      val != 0 else {
        break
    } 
    returnString.append(Character(UnicodeScalar(UInt8(val))))
  }
  
  return returnString
}

Using the Mirror object, you can take a tuple of any size and iterate over it. It’s much cleaner than hard coding to a parameter of type Tuple with 16 Int8’s.

Jump back down to the main body, and replace print("\t\(segmentCommand.segname)") with the following:

let segName = convertIntTupleToString(
  name: segmentCommand.segname)
print("\t\(segName)")

Build and run.

0 MachOPOC 0x0000000100000000
  __PAGEZERO
  __TEXT
  __DATA
  __LINKEDIT
1 libBacktraceRecording.dylib 0x0000000100ac7000
  __TEXT
  __DATA
  __LINKEDIT
2 libMainThreadChecker.dylib 0x0000000100ad7000
  __TEXT
  __DATA
  __LINKEDIT
  ...

Much better, right? Now each module will print out its containing Segments.

You’re almost there! The final hurdle with print out the remaining Sections for each Segment.

Right below the new print command you just created, add the following code:

for j in 0..<segmentCommand.nsects { // 1
  let sectionOffset = curLoadCommandIterator +
    MemoryLayout<segment_command_64>.size // 2
  let offset = MemoryLayout<section_64>.size * Int(j) // 3
  let sectionCommand = 
    UnsafePointer<section_64>(
      bitPattern: sectionOffset + offset)!.pointee

  let sectionName = 
    convertIntTupleToString(name: sectionCommand.sectname) // 4
  print("\t\t\(sectionName)") 
}

The final round of numeric explanations:

  1. In each struct segment_command_64, there’s a member that specifies the number of section_64 commands immediately following it. You’ll use another for loop to iterate over all the sections found in each segment.

  2. To start, you’re grabbing the base address of the first struct section_64 in memory.

  3. For each iteration in the for loop, you’ll start with the offset address then add the size of the struct section_64 multipied by the iterator variable j. If you add the sectionOffset + offset, you’ll get the correct section_64 address to reference.

  4. A struct section_64 also has a sectname variable that’s a tuple of Int8’s. You’ll throw the same function you created earlier to get a pretty Swift String out of it.

That’s it for code. Build and run. Included is a tiny snippet of the output you’ll get.

0 MachOPOC 0x0000000100000000
  __PAGEZERO
  __TEXT
    __text
    __stubs
    __stub_helper
    __cstring
    __objc_methname
    __const
    __swift4_types
    __swift4_typeref
    __swift4_reflstr
    __swift4_fieldmd
    __swift4_capture
    __swift4_assocty
    __swift4_proto
    __swift4_builtin
    __objc_classname
    __objc_methtype
    __swift4_protos
    __ustring
    __gcc_except_tab
    __unwind_info
    __eh_frame
  __DATA
    __nl_symbol_ptr
    __got
    __la_symbol_ptr
    __mod_init_func
    __const
    __cfstring
    __objc_classlist
    __objc_nlclslist
    __objc_catlist
    __objc_protolist
    __objc_imageinfo
    __objc_const
    __objc_selrefs
    __objc_protorefs
    __objc_classrefs
    __objc_superrefs
    __objc_ivar
    __objc_data
    __data
    __crash_info
    __thread_vars
    __thread_bss
    __bss
    __common
  __LINKEDIT

As you can see, only the main executable has the __PAGEZERO segment, which has 0 Sections. There’s a slew of sections that contain swift4 in them. There’s a bunch of Objective-C related sections in the __DATA segment since Swift can’t survive without Objective-C on Apple platforms.

In the next chapter, you’ll look at some of these sections more closely and do some much more amusing things with the knowledge you got from this chapter.

Where to go from here?

If I haven’t indirectly hinted it enough, go check out mach-o/loader.h. I’ve read that header many times myself, and each time I read it I still learn something new. There’s a lot there, so don’t get frustrated if this chapter knocked you back into your chair.

Play with all the variables with the structs you created. Check out the other load commands and match them with the appropriate structs. Add these commands to the demo project you created and see what information you can pull out of them.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.