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
CoreFoundationmodule, 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_headerright at the beginning containing two, 4-byte members calledmagicandnfat_arch. Thenfat_archhas a value of0x03000000, but since you know it’s byte-swapped, the actual value is0x00000003. This brings the total count to 8 bytes so far from this header. -
Immediately following the
fat_headerare threestruct fat_archs (becausenfat_archis 3). Thefat_archis 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 4option wouldn’t have worked since there are a couple of 8-byte variables mixed with 4-byte ones instruct 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__PAGEZEROload 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
__TEXTsegment 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
__DATAsegment 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:
-
Although
Foundationwill indirectly import theMachOmodule, you are explicitly importing theMachOmodule 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. -
The
_dyld_image_countfunction 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. -
The
_dyld_get_image_namefunction will return the full path of the image. -
The
_dyld_get_image_headerwill return the load address of the Mach-O header (mach_headerormach_header_64) for that current module. -
The
CFRunLoopRunwill 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:
-
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_64to determine where the load commands start. A good program would check if it’s running a 32-bit mode by determining themagicvalue, but it’s fun to walk on the wild side occassionally… -
Using Swift’s
UnsafePointerto cast the load command to the “generic”load_commandstruct that you saw earlier. If this struct contains the correctcmdvalue, you’ll cast this memory address to the appropriatesegment_command_64struct. -
Here you know that the
load_commandstruct should actually be asegment_command_64struct, so we’re using Swift’sUnsafePointerobject again. -
At the end of each loop, we need to increment the
curLoadCommandIteratorvariable by the size of the currentloadCommand, which is determined by itscmdsizevariable.
Note: How did I know to cast the
segment_command_64struct when I saw the valueLC_SEGMENT_64? In themach-o/loader.hheader, search for all references toLC_SEGMENT_64. There’s the declaration that definesLC_SEGMENT_64and then there’s thesegment_command_64which states itscmdisLC_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:
-
In each
struct segment_command_64, there’s a member that specifies the number ofsection_64commands immediately following it. You’ll use anotherforloop to iterate over all the sections found in each segment. -
To start, you’re grabbing the base address of the first
struct section_64in memory. -
For each iteration in the
forloop, you’ll start with the offset address then add the size of thestruct section_64multipied by the iterator variablej. If you add thesectionOffset + offset, you’ll get the correctsection_64address to reference. -
A
struct section_64also has asectnamevariable that’s a tuple ofInt8’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.