CryptedHelloWorld: App with encrypted mach-o sections

Posted: July 23rd, 2016 | Author: | Filed under: crypto, macOS, Programming | Tags: , , , , , , , | No Comments »

In a previous post ( constructor and destructor attributes ), I described the constructor attribute and mentioned software protection as a possible use case:

A constructor attribute could be used to implement a software protection. You could encrypt your executable with a custom encryption and use a constructor function to decrypt the binary just before it is loaded.

In this post I describe such a protection with an example.

Mach-O file format

Let’s start with a brief summary of the Mach-O file format. For more information you should definitively read the OS X ABI Mach-O File Format Reference.

A Mach-O file contains 3 regions:

  • header structure: describes the Mach-O file
  • load commands: describes the segments and their sections
  • actual segment data

Here is the figure from the OS X ABI Mach-O File Format Reference:

Mach-O file format basic structure

The goal is to encrypt the data of the __text section from the __TEXT segment. This is the section containing the code of the executable. The other sections and segments will be left untouched.

The target application: CryptedHelloWorld

The target application is called ‘CryptedHelloWorld’. Its (__TEXT, __text) section is encrypted, meaning that the main() function needs to be decrypted before running. When launched, a constructor function will decrypt the encrypted section and the decrypted main() will be called.

The application itself is a simple command line ‘Hello World’ written in C. Here is the source code of the main function:

int main(int argc, const char * argv[])
	printf("Hello, World!\n");
    return 0;


When building the project with Xcode, the target dependency ‘CryptoTool’ is built. At the end of the compilation of the CryptedHelloWorld app, a Run Script phase is executed which runs ‘CryptoTool’ on the just compiled CryptedHelloWorld binary. ‘CryptoTool’:

  • reads the binary of the CryptedHelloWorld application from the disk
  • locates the (__TEXT, __text) section in the file
  • encrypts it using AES 128
  • replaces the bytes with the encrypted bytes in the binary on the disk.

The Run Script phase is straightforward:

Run Script phase

Location of the constructor function

When you launch the application, the constructor function will be triggered and will need to decrypt the (__TEXT, __text) section. Obviously this constructor function can’t be located in the (__TEXT, __text) section. I store it in a custom (__TEXT,__timac) section using the attribute __attribute__((section(“__TEXT,__timac”))):

void __attribute__((constructor)) __attribute__((section("__TEXT,__timac"))) decryptTextSection()

Self contained constructor function

We need to make sure that the constructor function doesn’t call any functions that are located in the (__TEXT, __text) section. To ensure that I made all the required functions inlined. For example:

static inline void __attribute__((always_inline)) EncryptDecryptMachoFile(struct mach_header_64 *machHeader, CCOperation operation)

The attribute __attribute__((always_inline)) ensures that the function is inlined even for debug builds when optimizations are turned off.

Locating the (__TEXT, __text) section

The constructor function uses the _dyld_get_image_header() dyld function to get the Mach-O header. It then loops though all the commands and all the sections of the LC_SEGMENT_64 segments to find the (__TEXT, __text) section:

static inline void __attribute__((always_inline)) EncryptDecryptMachoFile(struct mach_header_64 *machHeader, CCOperation operation)
	size_t segmentOffset = sizeof(struct mach_header_64);
	// For each load command of the mach-o file
	for (uint32_t i = 0; i < machHeader->ncmds; i++)
		struct load_command *loadCommand = (struct load_command *)((uint8_t *) machHeader + segmentOffset);
		if(loadCommand->cmd == LC_SEGMENT_64)
			// We found a 64-bit segment
			struct segment_command_64 *segCommand = (struct segment_command_64 *) loadCommand;
			// For each section in the 64-bit segment
			void *sectionPtr = (void *)(segCommand + 1);
			for (uint32_t nsect = 0; nsect < segCommand->nsects; ++nsect)
				struct section_64 *section = (struct section_64 *)sectionPtr;
				fprintf(stderr, "Found the section (%s, %s)\n", section->segname, section->sectname);
				// Check if this is the __TEXT segment
				if (strncmp(segCommand->segname, SEG_TEXT, 16) == 0)
					// Check if this is the __text section
					if (strncmp(section->sectname, SECT_TEXT, 16) == 0)
						// This is the (__TEXT, __text) section.
						// We should encrypt/decrypt it in place.
						fprintf(stderr, "%s the (%s, %s) section\n", (operation == kCCEncrypt) ? "Encrypting" : "Decrypting", section->segname, section->sectname);
						EncryptDecryptBuffer((uint8_t *) machHeader + section->offset, (uint8_t *) machHeader + section->offset, section->size, operation);
				sectionPtr += sizeof(struct section_64);
		segmentOffset += loadCommand->cmdsize;


The section is encrypted using AES 128 by chunks of PAGE_SIZE bytes (4096 bytes). If there are less than PAGE_SIZE bytes to encrypt, chunks of 16 bytes are used. I use the CommonCrypto implementation of AES 128:

static inline void __attribute__((always_inline)) EncryptDecryptBytes(const void *srcBuffer, void *dstBuffer, uint64_t len, CCOperation operation)
	// Encrypt/decrypt the data from the source buffer using the AES key
	size_t outLength = 0;
	CCCryptorStatus result = CCCrypt(operation,
                   len + kCCBlockSizeAES128,
	if (result == kCCSuccess)
		// Copy the encrypted/decrypted data into the destination buffer
		memcpy(dstBuffer, sEncryptionBuffer, len);
		fprintf(stderr, "Error %d: Could not %s the data\n", result, (operation == kCCEncrypt) ? "encrypt" : "decrypt");

Virtual memory protections

The __TEXT segment is not writable by default. In order to decrypt the memory in place, the virtual memory protections need to be changed to allow writes. This is done using vm_protect:

static inline void __attribute__((always_inline)) ChangeVirtualMemoryProtections(vm_address_t addr, vm_size_t size)
	kern_return_t returnValue = vm_protect(mach_task_self(), addr, size, false, VM_PROT_ALL);
	if ( returnValue != KERN_SUCCESS )
		fprintf(stderr, "Error %d: Fail to change virtual memory protections\n", returnValue);

Testing the compiled application

Here is the log output when launching the binary using the Terminal:

*** Constructor called to decrypt sections
Found the section (__TEXT, __text)
Decrypting the (__TEXT, __text) section
Found the section (__TEXT, __stubs)
Found the section (__TEXT, __stub_helper)
Found the section (__TEXT, __timac)
Found the section (__TEXT, __cstring)
Found the section (__TEXT, __unwind_info)
Found the section (__DATA, __nl_symbol_ptr)
Found the section (__DATA, __got)
Found the section (__DATA, __la_symbol_ptr)
Found the section (__DATA, __mod_init_func)
Found the section (__DATA, __data)
Found the section (__DATA, __bss)
*** The sections should now be decrypted. main() will be called soon.

Hello, World!

Examining the compiled application

Using MachOView, we see that the (__TEXT, __text) section is encrypted:

Section __text

This is confirmed with Hopper. The main() function doesn’t make any sense:


Back to MachOView, we see that the (__TEXT, __timac) section contains unencrypted code:

Section __timac

Limitations of this proof of concept

As mentioned this example is a proof of concept and has several limitations:

  • it only supports 64-bit Mach-O files. Adding 32-bit and fat Mach-O support is fairly simple and left to the reader.
  • only the (__TEXT, __text) section is encrypted. It is possible to encrypt other sections or maybe even the whole __TEXT segment.
  • the target application is a really simple command line application written in C.
  • As mentioned earlier all the functions required in the constructor function have been made inlined. This makes it difficult to debug. If you want to debug this code I recommend to debug the CryptoTool app. It supports the parameters -decrypt and -encrypt. You should also remove the always_inline attribute otherwise breakpoints won’t fire as you would expect.

Should you use such code to protect your application?

I wouldn’t. Although this might prevent a user to look at the code, this won’t defeat an experienced attacker. Also note that this is a proof of concept.


You can download the precompiled CryptedHelloWorld command line tool here.

The whole source code is available here.

Blowfish operations with key size longer than 448 bits in macOS 10.11.5 / iOS 9.3.2

Posted: July 10th, 2016 | Author: | Filed under: iOS, macOS, Programming | Tags: , , , , | No Comments »

Until macOS 10.11.4 and iOS 9.3.1 CommonCrypto/corecrypto supported Blowfish operations with key sizes longer than 448 bits. Starting with macOS 10.11.5 and iOS 9.3.2 this is no longer the case: the minimum and maximum key sizes are now enforced (respectively kCCKeySizeMinBlowfish 8 bytes and kCCKeySizeMaxBlowfish 56 bytes).

This is probably the fix for CVE-2016-1802:


If you perform a Blowfish operation with a key length longer than 448 bits, it will now fail with an error kCCParamError. Below is an example of code using a 64 bytes Blowfish key that works on macOS 10.11.4 / iOS 9.3.1 but returns an error kCCParamError on newer systems:

#import <Foundation/Foundation.h>
#import <CommonCrypto/CommonCrypto.h>

int main(int argc, const char * argv[])
		uint8_t keyData[64] =
			0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
			0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF,
			0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
		CCCryptorRef cryptorRef;
		CCCryptorStatus status = CCCryptorCreate(kCCDecrypt, kCCAlgorithmBlowfish, 0, keyData, sizeof(keyData), NULL, &cryptorRef);
		NSLog(@"CCCryptorCreate result: %d", status);
		if(status != kCCSuccess)
			NSLog(@"*** CCCryptorCreate failed!!!");
    return 0;

If you have to support Blowfish with a key longer than 448 bits, you can’t use anymore CommonCrypto and should switch to a different implementation. Note that using Blowfish with a key longer than 448 bits is not recommended as it weakens the security guaranteed by the algorithm.