Friday, October 25, 2013

iOS Binary Format

The man page for the codesign tool gives insight into the format of the binary that might appear on an iPhone.

When verifying a code signature on code that has a universal ("fat") Mach-O binary, separately verify each architecture contained.

The architecture can be specified either by name (e.g. i386) or by number. This option applies only to Mach-O binary code and is ignored for other types.

Here's an example usage:

/usr/bin/codesign -d --verbose /xcode/yourProj.app/yourProj
Executable=/xcode/yourProj.app/yourProj
Identifier=com.yourShop.yourProj
Format=bundle with Mach-O thin (armv7)
CodeDirectory v=20100 size=5087 flags=0x0(none) hashes=246+5 location=embedded
Signature size=4335
Authority=iPhone Developer: John Doe (1234567890)
Authority=Apple Worldwide Developer Relations Certification Authority
Authority=Apple Root CA
Signed Time=2013-10-24 4:39:51 PM
Info.plist entries=26
Sealed Resources rules=2 files=9
Internal requirements count=2 size=488

sensepost says:

iPhone apps are based on Mach-O (Mach Object) file format which is composed of a Header then Load Commands then Data.

iPhone apps are normally encrypted and are decrypted by the iPhone loader at run time.

The blah.app is not the actual executable. If you browse this folder, you will find a binary file named blah. This is the actual application binary.

Use otool ...

Here's an example usage:

otool -l /xcode/yourProj.app/yourProj | grep crypt
cryptoff  4096
cryptsize 720896
cryptid   0

According to sensepost, cryptid 0 indicates that this binary is not encrypted. The difference is that Saurabh is executing on a jailbroken iPhone whereas I'm executing on a macmini and my binary is a developer build. This indicates that the final binary on the phone as delivered from the appstore is materially different from the one you produce as a developer.

The steps from sensepost indicate that in general the app on disk on the iPhone is encrypted and is not decrypted until the iOS loads it into RAM. Saurabh gets a decrypted version by dumping the RAM and replacing the encrypted content on disk and flipping the cryptid flag to zero.

One assumes that the binary is encrypted when it is copied onto the iPhone and that the decryption key is unique to that particular device. Thus the publisher of an app can't know in advance the actual bytes of their binary. Thus if the publisher wants to independantly verify those bytes at runtime they must look at RAM not at disk.

man otool
 -h  Display the Mach header.
 -l  Display the load commands.
otool -h /xcode/yourProj.app/yourProj
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
 0xfeedface      12          9  0x00          2    25       2880 0x00218085
otool -l /xcode/yourProj.app/yourProj
Load command 0
      cmd LC_SEGMENT
  cmdsize 56
  segname __PAGEZERO
   vmaddr 0x00000000
   vmsize 0x00001000
  fileoff 0
 filesize 0
  maxprot 0x00000000
 initprot 0x00000000
   nsects 0
    flags 0x0
Load command 1
      cmd LC_SEGMENT
  cmdsize 668
  segname __TEXT
   vmaddr 0x00001000
   vmsize 0x000b1000
  fileoff 0
 filesize 724992
  maxprot 0x00000005
 initprot 0x00000005
   nsects 9
    flags 0x0
Section
  sectname __text
   segname __TEXT
      addr 0x00002954
      size 0x000965b4
    offset 6484
     align 2^2 (4)
    reloff 0
    nreloc 0
     flags 0x80000400
 reserved1 0
 reserved2 0
Section
  sectname __stub_helper
   segname __TEXT
      addr 0x00098f08
      size 0x0000042c
    offset 622344
     align 2^2 (4)
    reloff 0
    nreloc 0
     flags 0x80000400
 reserved1 0
 reserved2 0
...
Load command 2
      cmd LC_SEGMENT
  cmdsize 1212
  segname __DATA
   vmaddr 0x000b2000
   vmsize 0x00017000
  fileoff 724992
 filesize 36864
  maxprot 0x00000003
 initprot 0x00000003
   nsects 17
    flags 0x0
Section
  sectname __lazy_symbol
   segname __DATA
      addr 0x000b2000
      size 0x00000270
    offset 724992
     align 2^2 (4)
    reloff 0
    nreloc 0
     flags 0x00000007
 reserved1 156 (index into indirect symbol table)
 reserved2 0
...
Load command 11
       cmd LC_MAIN
   cmdsize 24
  entryoff 6485
 stacksize 0
Load command 12
          cmd LC_ENCRYPTION_INFO
      cmdsize 20
    cryptoff  4096
    cryptsize 720896
    cryptid   0
Load command 13
          cmd LC_LOAD_DYLIB
      cmdsize 52
         name /usr/lib/libstdc++.6.dylib (offset 24)
   time stamp 2 Wed Dec 31 19:00:02 1969
      current version 56.0.0
compatibility version 7.0.0
...
Load command 20
          cmd LC_LOAD_DYLIB
      cmdsize 92
         name /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation (offset 24)
   time stamp 2 Wed Dec 31 19:00:02 1969
      current version 793.0.0
compatibility version 150.0.0
Load command 21
      cmd LC_FUNCTION_STARTS
  cmdsize 16
  dataoff 848860
 datasize 3128
Load command 22
      cmd LC_DATA_IN_CODE
  cmdsize 16
  dataoff 851988
 datasize 48
Load command 23
      cmd LC_DYLIB_CODE_SIGN_DRS
  cmdsize 16
  dataoff 852036
 datasize 428
Load command 24
      cmd LC_CODE_SIGNATURE
  cmdsize 16
  dataoff 1004544
 datasize 15040

Details From OS X ABI Mach-O File Format Reference:

A Mach-O file contains code and data for one architecture. The header structure of a Mach-O file specifies the target architecture, which allows the kernel to ensure that, for example, code intended for PowerPC-based Macintosh computers is not executed on Intel-based Macintosh computers.

You can group multiple Mach-O files (one for each architecture you want to support) in one binary using the format described in Universal Binaries and 32-bit/64-bit PowerPC Binaries.

Binaries that contain object files for more than one architecture are not Mach-O files. They archive one or more Mach-O files.

The static linker creates a __PAGEZERO segment as the first segment of an executable file.

The __TEXT segment contains executable code and other read-only data. The read-only attribute also means that the pages that make up the __TEXT segment never need to be written back to disk.

The __DATA segment contains writable data.

struct mach_header
{
  uint32_t magic;
  cpu_type_t cputype;
  cpu_subtype_t cpusubtype;
  uint32_t filetype;
  uint32_t ncmds;
  uint32_t sizeofcmds;
  uint32_t flags;
};

The load command structures are located directly after the header of the object file, and they specify both the logical structure of the file and the layout of the file in virtual memory. Each load command begins with fields that specify the command type and the size of the command data.

struct load_command
{
  uint32_t cmd;
  uint32_t cmdsize;
};

In the above example we saw the following commands.

Load command 0
      cmd LC_SEGMENT
Load command 1
      cmd LC_SEGMENT
Load command 2
      cmd LC_SEGMENT
...
Load command 11
       cmd LC_MAIN
Load command 12
          cmd LC_ENCRYPTION_INFO
Load command 13
          cmd LC_LOAD_DYLIB
...
Load command 20
          cmd LC_LOAD_DYLIB
Load command 21
      cmd LC_FUNCTION_STARTS
Load command 22
      cmd LC_DATA_IN_CODE
Load command 23
      cmd LC_DYLIB_CODE_SIGN_DRS
Load command 24
      cmd LC_CODE_SIGNATURE

Of these, the apple document only describes the following.

Commands       Data structures  Purpose

LC_SEGMENT     segment_command  Defines a segment of this file to be mapped into the
                                address space of the process that loads this file. It also
                                includes all the sections contained by the segment.

LC_LOAD_DYLIB  dylib_command    Defines the name of a dynamic shared library that this file 
                                links against.

Data on the iPhone

iphonedevwiki says that AppStore apps are all encrypted when downloaded, to prevent reverse engineering, and ensure every account can only run their own copy.

stackoverflow has an example of someone reading LC_ENCRYPTION_INFO from the binary on disk to ensure that cryptid is set to ensure that it's not running a cracked version of itself. This serves as a good example for examining your own binary in general.

One of the comments claims: "[LC_ENCRYPTION_INFO] is telling you whether the binary is encrypted with Apple's FairPlay DRM. Any debug or ad-hoc builds you do will say NO."

He says that if you've synchedk yoru phone you can check under "~/Music/iTunes/Mobile Applications" and unzip an .ipa file and run otool against the binary to confirm that binaries fromt he apple store have cryptid set.

wikipedia says:

Apple no longer sell songs with FairPlay encryption, however, apps downloaded from the iTunes store are still encrypted with Fairplay.

The master key required to decrypt the content is itself stored in encrypted form in the container file. The key required to decrypt the master key is called the user key.

These descriptions make it seem very unlikely that a binary would be automatically decrypted on file read, as is the case with full-disk-encryption. It seems like the OS facility that launches a binary is the only component that is natively able to decrpyt the binary. Thus independant run-time integrity checks are probably impossible.

stackoverflow and puredarwin have some good details on inspection of LC_CODE_SIGNATURE, mainly with otool.

codesign -dvvvv /usr/bin/otool 
Executable=/usr/bin/otool
Identifier=com.apple.otool
Format=Mach-O universal (i386 ppc7400)
CodeDirectory v=20001 size=2920 flags=0x0(none) hashes=141+2 location=embedded
CDHash=ea392676d34975966fcc4471c3d85dee70978255
Signature size=4064
Authority=Software Signing
Authority=Apple Code Signing Certification Authority
Authority=Apple Root CA
Info.plist=not bound
Sealed Resources=none
Internal requirements count=0 size=12

github has a python project to parse the certs directly from a Mach-O file.

ios
{ "loggedin": false, "owner": false, "avatar": "", "render": "nothing", "trackingID": "UA-36983794-1", "description": "", "page": { "blogIds": [ 459 ] }, "domain": "holtstrom.com", "base": "\/michael", "url": "https:\/\/holtstrom.com\/michael\/", "frameworkFiles": "https:\/\/holtstrom.com\/michael\/_framework\/_files.4\/", "commonFiles": "https:\/\/holtstrom.com\/michael\/_common\/_files.3\/", "mediaFiles": "https:\/\/holtstrom.com\/michael\/media\/_files.3\/", "tmdbUrl": "http:\/\/www.themoviedb.org\/", "tmdbPoster": "http:\/\/image.tmdb.org\/t\/p\/w342" }