Resigning iOS apps from an IPA for mobile security research Feb 7 2020
When we are asked to perform a black-box security analysis on an iOS app, depending on the scope, we might only have access to the iOS app from the AppStore. But most of the time, the client would give us an IPA. In a black-box analysis, we won't get access to the source code, so deploying it through Xcode to a Device for testing might be out of the question. One possible solution is to resign the app with a profile that we own and deploy it to our test device. In this post, I'll explain how to re-sign an iOS app so we can generate an IPA that we can deploy to our test device.
Let's first talk about code signing. If you already understand how code signing works, you can skip the next section.
Table of Contents
As a security measure, Apple requires all code that runs on their devices to be digitally signed by a developer they trust. The signing works similar to how SSL certificates work on websites. I'll use this analogy because most people are used to the web, and the concepts (even if they don't know it by name) will make sense.
SSL certificate and CAs
To use HTTPS on your website, you need to have a digital certificate. That certificate has information about your site that anyone can verify. You use that certificate on your website to encrypt the communication between the users and your server. But your users need to be able to check the validity of the certificate that you are using. How can the user validate it?
To begin with, the certificate you get for your site should be signed by a trusted entity that verifies that you are who you say you are. Else, imagine, anyone could generate a digital certificate and pretend to be your bank. To this effect, your browser comes preloaded with many trusted Certificate Authorities (CAs), and your browser will only accept certificates signed (generated) by any of those CAs. Your browser validates the certificate on your website, by querying the CA.
To obtain a certificate, you first need to generate a CSR (Certificate Signing Request), where you specify the details of the certificate that you want. This CSR is sent to the CA. The CA verifies that the data you submitted is correct, they might ask you for proof of ownership of the domain, and any other documentation that identifies you as you. When they have verified all that, they'll generate and send the certificate back to you. You can use that certificate on your website, and everyone is happy and secure (overstatement).
That is the general idea on how to get an SSL certificate for your website, and the same concept applies to the iOS apps.
Apple as the only valid CA for iOS apps
So as you can probably imagine by now, or if you have created your Developer account with Apple, the process is pretty much the same. You get a certificate from a valid Certificate Authority (Only Apple in our case) after sending a CSR.
Now that you have your certificate, you can start signing your code digitally. Using the certificate to sign your code digitally will identify any app that you create. And Apple trusts it's your code because you are the only person who should have access to the private key for that certificate.
I hope that gives you a better sense of how the certificates work. I don't want to go into much detail because there are many good resources out there that explains this, and this post is not about understanding how code signing works. If the concept is still unclear, you can check Apple's code signing support page or the Code signing guide.
Ok, let's get started with resigning an iOS application. For that, we need an IPA file.
Extract the app bundle from the IPA
I'll assume that you have a
.ipa file, whether you used frida-ios-dump, or any other tool, is up to you. But you should have an IPA file that you can use. If you want to follow along, we are going to use the OWASP iGoat-Swift. You can download the iGoat-Swift_v1.0.ipa directly, or clone the project and extract the IPA file.
ios-deploy to sideload the app on our test device. Currently, if you try to sideload the IPA file as is, it'll fail because the provisioning profile doesn't include your device as a valid device to run your app in. That's the reason we are resigning the application. So let's get back to extracting the IPA.
The IPA file can be unpacked treating the file as a ZIP file. File extensions mean little but for the sake of clarity, let's change the extension.
1 2 3 $ mv iGoat-Swift_v1.0.ipa iGoat-Swift_v1.0.zip $ unzip iGoat-Swift_v1.0.zip -d iGoat-Swift # this should create a directory iGoat-Swift with Payload inside
We need first check what entitlements the current IPA needs so we'll extract the entitlements first.
Extracting the entitlements
Each application installed on a device requires a provisioning profile. These profiles are created on Apple's developer site. I'll assume you already have one. Later, we are going to replace your provisioning profile for the one currently on the IPA, but for now, we need to explore the current provision profile to see the requirements of the profile we need to create for ourselves.
First, we'll extract a plist from the
embedded.mobileprovision. Let's work on the
1 2 3 4 # We should have the following directory structure iGoat-Swift └── Payload └── iGoat-Swift.app
So we are going to get the
embedded.mobileprovision inside the application bundle:
1 2 $ cd iGoat-Swift $ security cms -D -i Payload/iGoat-Swift.app/embedded.mobileprovision > provision.plist
You can jump into your favourite editor if you want to read the
plist, or we can extract all entitlements section from the
1 2 3 4 5 6 7 # we'll use tee to see the output on the screen and also write it to the # entitlements.plist file $ /usr/libexec/PlistBuddy -x -c 'Print :Entitlements' provision.plist | tee entitlements.plist # we could have also done it the long and boring way if you feel # like typing more. $ /usr/libexec/PlistBuddy -x -c 'Print :Entitlements' provision.plist > entitlements.plist $ cat entitlements.plist
What you'll see is something similar to this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?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>application-identifier</key> <string>6J6AZQ7T23.*</string> <key>com.apple.developer.team-identifier</key> <string>6J6AZQ7T23</string> <key>get-task-allow</key> <true/> <key>keychain-access-groups</key> <array> <string>6J6AZQ7T23.*</string> </array> </dict> </plist>
Now is when your experience as a Developer helps, or google. If you've worked with entitlements before and know what they mean, you'll know which entitlements you need to create. If you don't know them by heart (I don't know anyone who does), use google to verify. I'll save you some time:
get-task-allow- allows you to run the app on debug mode, it gets added when we run it from Xcode.
keychain-access-groups- Allows you to share keychain items between app groups.
com.apple.developer.team-identifier are generated when you select the entitlement for
keychain-access-groups in Xcode.
We now have everything we need, "recon" step done, let's move on.
Creating an empty-shell app
We are going to create an empty Xcode project that we are going to run on our device. This app should have the same entitlements as the app that we are going to resign. The resign part can be misleading. It implies we are going to use the same app but with a different signature. I think it's useful to think that we are going to use a different app, and put the code of the previous app in there.
We can't change some parts of the app and keep the signature intact, that is the whole purpose of digitally signing the code.
Ok, so the entitlement we need for the iGoat-Swift app is the "Keychain access groups", search for it on the project information entitlements section and add it.
Build and run your empty application. After running the app on your test device, you'll have the correct provision profile that will include your device ID.
Now, you can remove the empty app from your test device. We only needed Xcode to create the correct provisioning profile. Now we'll use that profile to "resign" the iGoat-Swift app.
Obtaining the correct profile.
Now let's obtain the entitlements from the empty-shell Application. We need to find where Xcode saved the application bundle for the app we just built.
To do that, go to the Xcode's file navigation, find "Product", click on the app. Then, check the Xcode Inspector area (right side panel on the Xcode Interface), and you'll find the path to the application bundle.
Inside the application bundle, you'll find the
embedded.mobileprovision. Copy the provision file to our working directory:
1 $ cp PATH_YOU_GOT_FROM_XCODE/embedded.mobileprovision new_embedded.mobileprovision
We can now obtain the correct entitlements from that provision file:
1 2 $ security cms -D -i new_embedded.mobileprovision > new_provision.plist $ /usr/libexec/PlistBuddy -x -c 'Print :Entitlements' new_provision.plist | tee new_entitlements.plist
Now we can remove the previous code signature:
1 $ rm -r Payload/iGoat-Swift.app/_CodeSignature
And resign everything using the entitlements we obtain from our empty Xcode project. To re-sign, you'll need to specify your Provision profile. You can find your provision profile in your Keychain (if you registered as an Apple developer). You can access it by using the
1 $ security find-identity -v -p codesigning
You'll see your provision profiles, use the same that you used to build and run your empty-shell application on your testing device.
With that information let's resign the application:
1 2 3 4 5 $ codesign -f -s "Your Provisioning Profile (AAAAAA)" --entitlements new_entitlements.plist Payload/iGoat-Swift.app/ #Code sign any framework you have $ codesign -f -s "Your Provisioning Profile (AAAAA)" --entitlements new_entitlements.plist Payload/iGoat-Swift.app/Frameworks/* #And code sign the main Executable $ codesign -f -s "Your Provisioning Profile (AAAA)" --entitlements new_entitlements.plist Payload/iGoat-Swift.app/iGoat-Swift
That will sign the code using your provisioning profile and with the correct entitlements. Now we can generate an
IPA from the newly signed application bundle.
1 $ zip -qr iGoat-Swift_v1.0.ipa Payload/
ios-deploy, we'll be able to sideload (install) the iOS app on your test device without any problem.
1 $ ios-deploy -b iGoat-Swift_v1.0.ipa
That's it. You should have the application working on your Device. Congratulations!
We managed to resign an application with our provisioning profile, but remember we need to match the original provisioning profile including its entitlements.
I also recommend you to read the Apples Code signing guide. The guide will give you a better understanding of how Apple handles code signing, and also it explains the concept of "Requirements" that you might find interesting.
Also, I encourage you to check the man page for
security(1) both full of handy options when dealing with code signing and provisioning profiles. With
security you have access to the keychain, if you want to see another example of using
security check my post on Understanding SSH Keys and using Keychain to manage passphrase on macOS .
Ok, that's it for this post. I hope it was useful, don't forget to subscribe to the Newsletter (in the footer of the site) to get notified when a new post is out. I also share info on topics I find interesting on the newsletter..
Related topics/notes of interest
- Apple's Code signing documentation.
- Apples Code signing guide.
- Vulnerable iOS application to learn mobile app security - OWASP iGoat-Swift.
- If you want to start learning some mobile security, check OWASP Mobile Testing Guide.