As of October 1, 2023, LINE has been rebranded as LY Corporation. Visit the new blog of LY Corporation here: LY Corporation Tech Blog

Blog


How AIR ARMOR checks iOS Code Signing

AIR ARMOR is a security solution of AIR; AIR is a component of the LINE GAME PLATFORM.

You can refer to Seunghoon's posting on AIR GO and APK Signing for information on the APK signature scheme. For this posting, I'll explain about iOS code signing, one of Apple's security mechanism. Code signing is to verify the integrity of the software code and confirm the signer (who wrote the code). I'll also cover checking the integrity of  iOS Mach-O binaries later using the code signature struct. Let me first give you some ideas about iOS code signing. 

What is iOS code signing?

iOS devices only run apps that are code signed with the certificate issued by Apple, whether it is an app downloaded from App Store or app built for the purpose of testing. This code signing must be done with a certificate issued by Apple. 

Apps downloaded from App Store are code signed with Apple's certificate as follows:

Those apps under development for distribution or testing by developers are signed with the development certificate issued by Apple.

The following is a sample of development certificate for ad hoc distribution.

To install an app signed with an Apple's Development Certificate, you must have a provisioning profile on your device. You need to have a provisioning profile installed on the device specified in the provisioning profile. Only then can you install and run an iOS app signed with the developer certificate.

What is provisioning profile?

Apple defines a provisioning profile as "a type of system profile used to launch one or more apps on devices and use certain services". You can create a provisioning profile in Xcode by enabling automatic signing (personally recommended) or go for manual signing at Apple Developer Program.

The following window appears for ad hoc distribution of a sample app.

When you select the Automatically manage signing option in the window above, Xcode will automatically generate a provisioning profile during the build. You can check automatically generated profiles in the following path: ~/Library/MobileDevice/Provisioning Profiles.

$ ls ~/Library/MobileDevice/Provisioning\ Profiles
1axxxx49-xxxx-xxxx-xxxx-c2xxxxxxxxff.mobileprovision 
a8xxxxec-xxxx-xxxx-xxxx-5cxxxxxxxx22.mobileprovision

If you select the Manually manage signing option, you need to choose a provisioning profile you created manually with your developer account, as shown below. However, you cannot make changes to automatically generated provisioning profiles and save them as custom provisioning profiles.

What does a provisioning profile contain?

You can find the provisioning profile in the app package file (.ipa file). When you unzip the .ipa file, you will see a file named embedded.mobileprovision in the Payload folder (e.g. Payload/sample.app/embedded.mobileprovision). This is your provisioning profile. You can use the following command to inspect the provisioning profile or just open the profile in a text editor.

$ security cms -D -i embedded.mobileprovision

Provisioning profiles have information on development certificatesprovisioned devicesentitlements, and expiration dates. If where you launch the app doesn't match the information in the provisioning profile, your app won't be launched.

Development certificates

When you take a look at the DeveloperCertificates key in a provisioning profile, you will find a string encoded in base64

As openssl command cannot interpret data encoded in base64, you need to convert this into PEM (Privacy-Enhanced Mail) format so that the command can understand the data. You can do this by copying the string, adding -----BEGIN CERTIFICATE----- at the top and -----END CERTIFICATE----- at the bottom, and saving the file. (For this posting, I saved the file as "sample.txt.")

Using the openssl command, you can check the information of the certificate saved in sample.txt as follows:

$ openssl x509 -in sample.txt -text
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number: XXXXXX... (XXXXXX... )
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = US, O = Apple Inc., OU = Apple Worldwide Developer Relations, CN = Apple Worldwide Developer Relations Certification Authority
        Validity
            Not Before: Oct 26 09:45:00 2018 GMT
            Not After : Oct 26 09:45:00 2019 GMT
        Subject: UID = XXXXXXXX, CN = iPhone Developer: XXXXXX (XXXXXXXX), OU = XXXXXXXX, O = XXXXXX, C = US
        Subject Public Key Info:
...

Entitlements

After checking the certificate information, you can find entitlements information, as shown below, right underneath the certificate information.

Entitlements specify which services the app is allowed to use and under what situation. For your information, as this provisioning profile sample is a debug build, the get-task-allow option is set as true to allow process debugging.

What is listed in the <array> above depends on what capabilities you selected in the Xcode Capabilities tab. Let me enable Access WiFi Information and Apple Pay capabilities as follows:

When you create a provisioning profile with the configurations above, you can see these capabilities are added to the Entitlements as keys with respective values.

Expiration dates

You can check the expiration date of a provisioning profile with the ExpirationDate key. Your app will not launch after this expiration date.

Provisioned devices

You can view a list of registered devices for this provisioning profile under the ProvisionedDevices key. Your app will only run on these devices.

Provisioned devices

A provisioning profile that allows an app to run on any device, for such as ad hoc distribution, will have the ProvisionsAllDevices key as true.

ProvisionsAllDevices

Verifying the integrity of binaries

Code signed Mach-O binaries have a code signature struct, which is used to verify the integrity of the Mach-O binaries.

What is a Mach-O binary file?

Before we take a deep dive, let me briefly explain about Mach-O binary files. Apps that run on iOS (.ipa files) have a Mach-O binary file. Mach-O is a standard executable format in iOS and macOS. iOS executes a Mach-O binary file to launch an app.

$ file sample
sample: Mach-O 64-bit executable x86_64
$ ./sample
sample!

How to find a Mach-O binary file

This is how you find a Mach-O binary file. First, you need to unzip the ipa file, and you will find the Info.plist file in the Payload folder (e.g. Payload/sample.app/Info.plist). When you open Info.plist, you will find the Executable file element, which is the Mach-O file executed to launch an app. The following is how the Info.plist file looks like.

The following diagram shows a high-level description of the structure of our sample Mach-O binary file.

Mach-O binary structure

Now, let's dive a little bit further into Mach-O binaries by looking at its components.

machHeader

You can check whether the file is a Mach-O file or not by looking at the magic value of the machHeader struct.

The following table shows a brief description of each member of the Mach-O Header struct. 

Member Description Example Reference
magic Indicates whether the file is a Mach-O file and determines the byte order. 0xfeedface: Mach-O 32 bit format and byte order matched to the endianness of the host PC <mach-o/loader.h>
cpu_type Indicates a type of CPU 0xc: arm <mach/machine.h>
cpu_subtype Indicates a subtype of CPU 0x9: arm v7 <mach/machine.h>
file_type Indicates a type of file for this header 0x2: Executable file <mach-o/loader.h>
num_load_commands Indicates the number of load commands. 34: Total of 34 load commands -
size_of_load_commands Indicates the total size of load commands in bytes. 3792: Total size of 34 load commands, which is 3792 bytes -
flags Indicates what optional features of the Mach-O file format this binary file is using. - <mach-o/loader.h>

Load commands

The machHeader struct is directly followed by a list of load commands (LoadCommand). If the result of parsing the machHeader struct indicates that the file is a Mach-O file, then the load command list is parsed. Load commands have a lot of information including the location of segments, the name of dynamic libraries and the location of symbol table. Among other things, I'll focus on the code signing related load command, which is LC_CODE_SIGNATURE (LoadCommand of CODE_SIGNATURE).

LoadCommandList

LC_CODE_SIGNATURE

You can see the LoadCommand related to code signing information of the Mach-O file.

You can look up the information on LC_CODE_SIGNATURE, using the otool command.

$ otool -l Payload/iOSSample-mobile.app/iOSSample-mobile  | grep LC_CODE_SIGNATURE -A3
      cmd LC_CODE_SIGNATURE
  cmdsize 16
  dataoff 3188480
 datasize 59808

dataoff indicates the offset of CodeSignature. When you follow this information one by one, you can find all of code signing related information. I'll not go into further detail about how to find this information.

The following shows a high-level structure of CodeSignature.

CodeSignature

Let's take a closer look at the following items that compose the CodeSignature struct:

CodeDirectory

In the CodeSignature struct, you can find the CodeDirectory, which has the hash values of a specific file or executable binary file pieces. When you look at the CodeDirectory struct of our sample app binary file, you can find HashSlot codeHash and HashSlot specialHash at the bottom of the list.

codeHash

codeHash has the hash values of the binary file for each pageSize(0x1000). The following diagram represents the codeHash of our sample app.

codeHash

When the code is modified, the hash value of the page corresponding to the code modifications will change. Since this hash value will not match to the hash value in the codeHash, iOS will know that the code has been modified. 

specialHash

Now, let's take a look at specialHash. We got the values of each element in the specialHash array by parsing the Mach-O file. The elements and each element value is as follows.

Array index Contains Description Hash (of our sample app)
0 Entitlement Bound in code signature 4b255acb014ab5dc8cd63f5120baeef19309e340
1 Application Specific Application Specific is not currently used as indicated by the code related to the Apple Mach-O code signing. 0000000000000000000000000000000000000000
2 Resource Directory Resource directory indicates _CodeSignature/CodeResources file in the ipa file. 35b65bb61f6b617e9a944cdada31cb78b47ab393
3 Internal requirements Internal requirements indicate Requirements in the file. cdbf07382a5a26998e34dc9d80070fc5db8c9230
4 Bound Info.plist (Manifest) Bound Info.plist indicates the Info.plist file in the .app directory. 5108d83c00eb7f294fb73914abdb0a1c977b92f2

CodeResources 

The resource directory points to the _CodeSignature/CodeResources file (e.g. Payload/sample.app/_CodeSignature/CodeResources) in the ipa file. The CodeResources file lists up the checksum of the resource files in the app file. You can see the content of the CodeResources file of our sample app below.

ResourceDirectory

We can check the hash value of sha-1 by using the shasum command. The hash value retrieve by the command is identical to the sample value identified in specialHash[2].

$ shasum Payload/iOSSample-mobile.app/_CodeSignature/CodeResources 
35b65bb61f6b617e9a944cdada31cb78b47ab393  Payload/iOSSample-mobile.app/_CodeSignature/CodeResources

Bound Info.plist

The "Bound Info.plist" indicates the Info.plist file in the .app directory. The Info.plist file has basic information on the app such as the name, version and filepath of the app icon. The value returned is identical to the sample value identified in specialHash[4].

$ shasum Payload/iOSSample-mobile.app/Info.plist
5108d83c00eb7f294fb73914abdb0a1c977b92f2  Payload/iOSSample-mobile.app/Info.plist

Requirements and Requirement

Requirement is a set of rules used to verify code signing. The number of Requirement is defined by the count member of the Requirements struct. You can check out the information on Requirements, using the codesign command as shown below.

$ codesign --display -r- Payload/iOSSample-mobile.app/iOSSample-mobile
Executable=Payload/iOSSample-mobile.app/iOSSample-mobile
designated => identifier "armor.sdk.sample.cocos2dx.ios" and anchor apple generic and certificate leaf[subject.CN] = "iPhone Distribution: XXXXXXX (XXXXXXX)" and certificate 1[field.1.2.840.113635.100.6.2.1] /* exists */

To verify the checksum, we will copy all of Requirements andRequirement fields and save it as a file, called requirements.txt.

Let's check the checksum of the requirements.txt file, using the shasum command as below. The value returned is identical to the sample value identified in specialHash[3].

$ shasum requirements.txt
cdbf07382a5a26998e34dc9d80070fc5db8c9230 requirements.txt

Entitlement

The CodeDirectory has the hash value for Entitlement in the binary file. You can find Entitlement in the CodeSignature struct, as shown below.

CodeDirectory - Entitlement

Let's copy the Entitlement struct, including the field magiclength and data, into a file. Save the file with the name, entitlements.txt.

Entitlement block

And run the shasum command with file containing the Entitlement struct. The value returned is identical to the sample value identified in specialHash[0].

$ shasum entitlement.txt
4b255acb014ab5dc8cd63f5120baeef19309e340 entitlement.txt

BlobWrapper

BlobWrapper has the CMS (Cryptographic Message Syntax) signature. You can view the information on BlobWrapper with jtool.

$ jtool --sig -v Payload/iOSSample-mobile.app/iOSSample-mobile
    ...
  Blob 4: Type: 10000 @41788: Blob Wrapper (4802 bytes) (0x10000 is CMS (RFC3852) signature) 

BlobWrapper has data on the code signing of the CodeDirectory as mentioned earlier and certificates used for code signing of such data. If you want to view the content of Certificates, extract the data of BlobWrapper from the binary file and save it as a file. In our exercise, let's name it blobwrapper.txt.

blobWrapper

You cannot open this text file with a text editor, but you can check overall content using the openssl command, as shown below.

$ openssl pkcs7 -inform der -in blobwrapper.txt -print -noout
PKCS7:
  type: pkcs7-signedData (1.2.840.113549.1.7.2)
  d.sign:
    version: 1
    md_algs:
        algorithm: sha256 (2.16.840.1.101.3.4.2.1)
        parameter: NULL
    contents:
      type: pkcs7-data (1.2.840.113549.1.7.1)
      d.data: <ABSENT>
    cert:
        cert_info:
          version: 2
          serialNumber: 134752589830791184
          signature:
            algorithm: sha1WithRSAEncryption (1.2.840.113549.1.1.5)
            parameter: NULL
          issuer: C=US, O=Apple Inc., OU=Apple Certification Authority, CN=Apple Root CA
          validity:
            notBefore: Feb  7 21:48:47 2013 GMT
            notAfter: Feb  7 21:48:47 2023 GMT
          subject: C=US, O=Apple Inc., OU=Apple Worldwide Developer Relations, CN=Apple Worldwide Developer Relations Certification Authority
          key:
            algor:
              algorithm: rsaEncryption (1.2.840.113549.1.1.1)
              parameter: NULL
            public_key:  (0 unused bits)
              0000 - 30 82 01 0a 02 82 01 01-00 ca 38 54 a6 cb   0.........8T..
...

When you change the content of the CodeDirectory, the CMS signature will change even if you use the same certificate for code signing. So even if an attacker changes the code and updates the content of the CodeDirectory in line with the changed code, you can ensure the integrity of your app by verifying the CMS signature with your key.

Conclusion

iOS does not stop at verifying the hash value of the files related to the binary file, but goes further to ensure the integrity of the app by verifying code signing. This process allows iOS to prevent a manipulated app from running on a device of random users.

Mach-O Binary file

AIR ARMOR provides additional security features on top of the basic security feature using the code signing principles of a Mach-O binary file. Please check out its security features here