White shape | Hexiosec Logo

So Macho - A look at Apple executable files

Scott Lester
2 December 2020
|
11 min Read
|
Scott Lester

Introduction

Apple code signing has been in the news lately, with the new macOS update having initial problems, as well as adding extra steps for developers building software.

Code signing and application permissions on Apple devices both rely on the Mach-O format, which is used for executable files on both macOS and iOS. This blog provides an overview of the structure of Mach-O binaries, and how they implement code signing and application permissions. We also introduce a new open-source tool we’ve written for parsing interesting information from Mach-O files.

Binary Executable Formats

Windows has its Portable Executable (.exe) files, Unix has its Executable and Linkable Format, but in the Apple world it’s all about Mach-O files, which is the format for regular executable files, as well as libraries and other supporting file types.

Mach-O files have some differences to the other formats, and are unique in some ways. For example, they are fat binaries, meaning they can contain implementations for multiple architectures in a single binary. This means a single binary can run on both 32 and 64-bit processors, or more topically on both ARM and Intel processors. This is simply done, with a wrapper around two or more Mach-O files in a single binary.

Let’s look at that structure in more detail.

Mach-O Files

Their structure is in three parts: a header, load commands, and segments (which contain sections). The diagram below shows the structure of an example Mach-O file

structure

Like many files, they start with a header that contains at its very beginning four bytes of (little-Endian) file magic:

File Magic

The file magics for different kinds of Mach-O files are listed, amongst other places, in open source Apple code

/* Constant for the magic field of the mach_header (32-bit architectures) */
#define	MH_MAGIC        0xfeedface	/* the mach magic number */
#define MH_CIGAM	0xcefaedfe	/* NXSwapInt(MH_MAGIC) */

/* 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) */

The same file also defines the structure of the header, which contains details on the binary. Here’s a parsed example the iOS ps binary:

Magic: 64-bit MachO
Type: Exec
CPU: AARCH64, ARM64
Commands: 32 (Size: 4040)
Flags: NoUndefs, DyldLink, TwoLevel, PIE

The header is followed by a list of variably-sized load commands, which specify information about the executable such as how to load its segments and run it. In the below example you can see load commands for the file’s segments, dynamic libraries (dyld), symbols, and file information such as a UUID:

load commands

At a minimum the commands contain a command type and size. Some standalone, and simply provide information such as details on libraries to load:

Library load command

Others simply contain instructions about how to load a segment, with an offset into the file and the data size. An example of this is the load command for a CODE_SIGNATURE section:

load command

The load commands are followed by the segments themselves, each of which contain one or more sections. For example, this __TEXT segment (segment names are uppercase, and prefixed with two underscores) contains many sections, such as __text, __stubs and __stubhelper (section names are lowercase, and also prefixed with underscores) etc:

example segment

Parsing Mach-O Files

If we want to analyse a Mach-O file there are GUI applications for browsing a file, and on macOS you can use the codesign tool to do lots with code signing. On the command line, the well-known jtool is a closed-source tool from Jonathan Levin, who writes Mac/iOS internals books and has published lots of information on Mach-O files and Apple code signing.

For some investigation work we needed to get information on Macho-O files programmatically, to do mass inspection on a dump of binaries. We didn’t want to script a solution around another tool, and couldn’t find anything for the job. So I wrote a Golang (we like Go, and had existing stuff into which we integrated this) tool called machodump to dump interesting information from a Mach-O binary, using the helpful Go-Macho library for low-level parsing.

P.S. If we’d done it in Python, C or even C++, the LIEF library is great for inspecting all the flavours of binary executables.

machodump

The code is available as a Go module on GitHub. It uses the go-macho package for parsing the file and outputs the interesting information to console.

If we run it on, for example, the macOS Chess binary, it dumps information on the file details, the imported libraries, interesting load commands, and all the signing information:

./machodump -i testfiles/Chess
2020/11/24 14:20:29 Parsing file "testfiles/Chess".
File Details:
        Magic: 64-bit MachO
        Type: Exec
        CPU: Amd64, x86_64
        Commands: 31 (Size: 4328)
        Flags: NoUndefs, DyldLink, TwoLevel, BindsToWeak, PIE
        UUID: 18455A71-F835-3D0F-8F7C-215BF86BC7AF
File imports 15 libraries:
        0: "/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration"
        1: "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation"
        2: "/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit"
        3: "/System/Library/Frameworks/GameKit.framework/Versions/A/GameKit"
        4: "/System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa"
        5: "/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL"
        6: "/System/Library/Frameworks/Carbon.framework/Versions/A/Carbon"
        7: "/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation"
        8: "/usr/lib/libobjc.A.dylib"
        9: "/usr/lib/libc++.1.dylib"
        10: "/usr/lib/libSystem.B.dylib"
        11: "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices"
        12: "/System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics"
        13: "/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices"
        14: "/System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO"
File has 31 load commands. Interesting commands:
        Load 11 (LC_SOURCE_VERSION): 369.0.0.0.0
Binary has 1 Code Directory:
        CodeDirectory 0:
                Ident: "com.apple.Chess"
                CD Hash: 9a95a73ca9b45ad1f0a603b0045c8baf256c289e959d491082acb063e81d30c9
                Code slots: 68
                Special slots: 5
                        Special Slot   5 Entitlements Blob:     560ea26d0d71b1927a954f02955932a7686c942730ecbf08736141c2e3893f00
                        Special Slot   4 Application Specific:  Not Bound
                        Special Slot   3 Resource Directory:    a5dc7f7078d841ff1783af7e5940e078e67952916c44eb5598092c133a6585e3
                        Special Slot   2 Requirements Blob:     0a7f89d52a71b16993861945aedc98a50019d9c84bf95e3cc4a88c37a68720ba
                        Special Slot   1 Bound Info.plist:      f7c596d135a14757e7444eacc9680924f52882b59d480fe4db5c40308e88cffa
CMS Signature has 3 certificates:
        CN: "Apple Code Signing Certification Authority"
        CN: "Apple Root CA"
        CN: "Software Signing"
Binary has 1 requirement:
        Requirement 0 (Designated Requirement): identifier "com.apple.Chess" and anchor apple
Binary has 5 boolean entitlements:
        com.apple.developer.game-center: true
        com.apple.security.app-sandbox: true
        com.apple.security.device.microphone: true
        com.apple.security.files.user-selected.read-write: true
        com.apple.security.network.client: true
Binary has 1 string array entitlement:
        0 com.apple.private.tcc.allow: ["kTCCServiceMicrophone"]
2020/11/24 14:20:29 Fin.

As you can see, the second half of the dump contains the details on code signing and entitlements, which is the detail we were really after in writing a tool. So let’s see how they are implemented in the Mach-O format.

Apple Code Signing

Mach-O files can contain a CODE_SIGNATURE segment, which contains all the information required to verify the integrity and origin of the file. In fact, all current versions of iOS require all executables to be signed, and this is now the case for Apple silicon macs running Big Sur:

New in macOS 11 on Macs with Apple silicon, and starting in macOS Big Sur 11 beta 6, the operating system enforces that any executable must be signed before it’s allowed to run. There isn’t a specific identity requirement for this signature: a simple ad-hoc signature is sufficient.

Apple Big Sur release notes

This is a way of making it harder to run malicious code; in Apple’s words to “enable the system to better detect code modifications”, plus it has the added benefit of giving Apple more control over the whole ecosystem. Of course it’s not the end of malicious code on macOS. All iOS exploits have for a while needed a mechanism to defeat code signing, and a few such mechanisms exist. This is just one aspect of the arms race between Apple and hackers, researchers and everyone else.

Let’s look at the layout and content of the code signing segment.

Code Signing Segment

The code signing segment contains a superblob structure, which itself contains other blobs of different types. The structure of both is defined in some more open-source Apple code:

/*
 * Structure of an embedded-signature SuperBlob
 */
typedef struct __BlobIndex {
	uint32_t type;					/* type of entry */
	uint32_t offset;				/* offset of entry */
} CS_BlobIndex;

typedef struct __SuperBlob {
	uint32_t magic;					/* magic number */
	uint32_t length;				/* total length of SuperBlob */
	uint32_t count;					/* number of index entries following */
	CS_BlobIndex index[];			/* (count) entries */
	/* followed by Blobs in no particular order as indicated by offsets in index */
} CS_SuperBlob;

The super blob just contains some magic, the length of the whole code signing section, a count of the blobs, and an array of indices to those blobs. The different identifying blob magics are defined in this file:

/*
 * Magic numbers used by Code Signing
 */
enum {
        kSecCodeMagicRequirement = 0xfade0c00,		/* single requirement */
        kSecCodeMagicRequirementSet = 0xfade0c01,	/* requirement set */
        kSecCodeMagicCodeDirectory = 0xfade0c02,	/* CodeDirectory */
        kSecCodeMagicEmbeddedSignature = 0xfade0cc0, /* single-architecture embedded signature */
        kSecCodeMagicDetachedSignature = 0xfade0cc1, /* detached multi-architecture signature */
        kSecCodeMagicEntitlement = 0xfade7171,		/* entitlement blob */
};

For example, the iOS ps binary has four blobs of content following the initial superblob:

CodeSign section key

These are, respectively:

  1. The code directory blob (magic 0xfade0c02), which you can spot as it has the qualified binary name com.apple.ps in it.
  2. An empty and short requirement set (magic 0xfade0c01).
  3. The entitlements blob, which contains the xml plist (magic 0xfade7171).
  4. A blob wrapper, (magic 0xfade0b01).

The final entry is a wrapper, a CSMAGIC_BLOBWRAPPER, which is defined in another source file. In this case it’s empty, but commonly is used to store a certificate chain in the form of a CMS blob. Here’s an example from the macOS Chess binary, in which certificate text is visible:

binary CMS blob

Three of these five blobs are of particular interest to us: the CodeDirectory blob containing the actual signing information, the CMS blob, and the Entitlement blob. All of them are detailed below.

CodeDirectory Blob

This blob (magic: 0xfade0c02) contains the signing information required to verify the integrity of the binary. This is implemented as a series of regular and special code slots containing hashes of different sections of code. This is all explained in proper detail in this presentation, but here’s an example blob from the iOS ps binary:

Binary has 1 Code Directory:
        CodeDirectory 0:
                Ident: com.apple.ps
                Team ID: 
                CD Hash: 8f6da51458c5d066a0578628506b2de08bfe3c67832dfc5a94a023ef2f1f46fb
                Code slots: 13
                Special slots: 5
                        Special Slot   5 Entitlements Blob:     f8d50f4e78b8be3234d0604b31ddf615a687ab089849ee15bde20daaeba4add0
                        Special Slot   4 Application Specific:  Not Bound
                        Special Slot   3 Resource Directory:    Not Bound
                        Special Slot   2 Requirements Blob:     987920904eab650e75788c054aa0b0524e6a80bfc71aa32df8d237a61743f986
                        Special Slot   1 Bound Info.plist:      Not Bound

Finally, the CD Hash is another crucial element for iOS (and now macOS) platform security, as when a system binary is run its CD Hash value is compared to a value stored in the iOS kernel, providing a further layer of security for the integrity of system binaries.

CMS Blob

The CMS blob (magic: 0xfade0b01) contains details on the certificate chain used to sign the binary. It stores PKCS7 encoded certificates, for example in the macOS Chess binary:

CMS Signature has 3 certificates:
        CN: "Apple Code Signing Certification Authority"
        CN: "Apple Root CA"
        CN: "Software Signing"

This is a typical Apple signed application. For third-party applications it will be the developer’s certificate, for example in the extra_recipe jailbreak binary:

CMS Signature has three certificates:
        CN: "Apple Worldwide Developer Relations Certification Authority"
        CN: "Apple Root CA"
        CN: "iPhone Developer: Luca Todesco (FHQ822AZ8S)"

Entitlements

The entitlements blob (magic: 0xfade7171) contains the list of entitlements that state what the application can access and do in the OS. Without the appropriate entitlement, applications cannot access certain device hardware or perform certain actions.

Android applications have a very similar model, with permissions, although they are designed to give the user the choice about what an application can access. A more accurate comparison would be system-only permissions on Android.

Taking the macOS Chess example from above, it has five binary entitlements:

com.apple.developer.game-center: true
com.apple.security.app-sandbox: true
com.apple.security.device.microphone: true
com.apple.security.files.user-selected.read-write: true
com.apple.security.network.client: true

These allow, respectively, the application to access the Game Center, to run in a sandbox, to use the device microphone, have read-write access to the user’s selected files, and access the network.

The ability to access or use these things is disallowed for applications that don’t have those entitlements, unless of course you break code signing enforcement.

The entitlements blob is easy to spot in a Mach-O file, as it contains the entitlements in an XML plist:

xml plist

Which, with nicer formatting, looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.system-task-ports</key>
	<true/>
	<key>task_for_pid-allow</key>
	<true/>
</dict>
</plist>

This is a simple example, from the iOS ps binary, containing just two key/boolean pairs for the enabled entitlements. A much more complicated example is a version of the binary for the iOS Springboard application, which manages everything on a phone, and has 460 entitlements.

Entitlement plists can also contain keys with paired integer, string and string array values. An example of string value entitlements are the identifiers we see in, for example the extra_recipe jailbreak binary:

application-identifier: YJ2B26Z4J4.com.example.extra-recipe
com.apple.developer.team-identifier: YJ2B26Z4J4

Conclusion

That’s it! We’ve hopefully provided a useful and interesting into Mach-O files, and shown how some of their content is crucial for implementing key security functions. For more useful and interesting information on their structure and on code signing, see the references below.

If you need someone to inspect lots of binaries for you, or if you’re interesting in technical incident response and investigation, get in touch.

References

About Scott Lester
Scott is a technical Cyber Security professional with over fifteen years' experience across a broad range of roles within the public and private sectors. With a deep understanding of cyber security, he has in his career focussed on applied cryptography, network technologies, digital forensics and security research. At Hexiosec he leads the delivery of all of our cyber security services.
Scott Lester