This week I have been busy trying to test some library we are developing. That's a bit hard problem having the code to test the library with real apps.
Scenario 1: Doing the test with a jailbreak device
Doing the test with jailbreak device is a bit easier, thanks to cycript, cycript is a tool that allow you to inject objective c code on a running process in your jailbreak device.
That's really amazing, it allows you to do whatever you want even rewrite some functions.
So we can use a NSBundle to load our library and just execute function of the library.
The question is how to create a valid NSBundle? that's actually the easy part. Any ipa is a NSBundle just that the executable inside has a flag saying if is a MHEXECUTABLE (2) MHBUNDLE (8), you can get all the values from loader.h which is the actual loader for mach-o files (the executable file format of macosx and iOS).
So we can create an empty app with the static library compiled inside and create the ipa like a normal app, decompress the ipa like it was a zip file, use machoview to see and modify the values of the mach-o header and recompress it. Now you have a shiny nice bundle to load.
Once done that we can just use the BundleWithURL: from cycript.
Scenario 2: Creating a valid IPA for other devices (will not pass the approval process)
The previous way of work works really great but has a big limitation, it only works on jailbreak devices that means that the QA team will need to have all the devices jailbreaked.
Other way of achieving this is by creating a dynamic library (like a .so on linux) and modifying the ipa headers to load it
for doing that first we will need to extract the ipa and this is only possible from a jailbreak phone, I use clutch to do all the hard work of loading app in memory, attach debugging and patch the encrypt memory section with the memory code.
Once we have our ipa on our computer to work, first step will be to sign it with our provision and developer certificate to make it possible to install on a non jailbreak device. I use this code basically you need to find the provision your are using on your normal development, you can find it on $HOME/Library/MobileDevice/Provision Profiles/ and the name of your developer certificate which you can found in your key chain.
Now you must be able to install the ipa on a non-jailbreak device, if the ipa is large I recommend to use iFunBox to install the app on the wire. If something goes wrong try to change the provisioning or the certificate name. If you feel lost install an empty app on your jailbreak phone and go to the /var/mobile/Applications folder find your app and do a
cat embedded.provisionprofile | grep UUID
Now we need to convert our bootstrap application with the static linked library to a dynamic library, again that is just changing some bits of the executable.
Export the app as an ipa and decompress it, and open the executable with omachview, on the offset 0xC you must change the "Data" of 2 to 0x9 (MH_DYLIB) also you must need to add a new load command.
To modify the executable I use radare2 (use with the -w argument to do modifications otherwise it opens the file read-only)
Now using the omachoview to see the offset and radare to patch the file. First find the last load load commands an on radare go to the offset on my example is 0xeac and now jump the command size with s +16. Now just we create the new load command as in the next image.
Finally the last two writes are for increasing the number of load commands in the Mach Header so you must increase by one the 0x10 position and by the size of the new command the 0x14 (in my case the increase was of 48 bytes)
Now you must save this file as libTest.dylib somewhere to use it later.
Now you just have to do something similar with your host app executable, so if your are trying to inject inside the twitter app you will need a copy of the executable inside the ipa and save it apart (i named it twitter-patch). And doing the same as before with the dynamic library just add a new LCLOADCOMMAND inside the app that load the @executablepath/libTest.dylib. If you made a mistake naming the library it's possible to use the installname_tool to change a dynamic library name on a executable without having to deal with load commands sizes.
Now the final step is to modify a bit the ipa_sign script so it will copy our libTest.dylib and Twitter-patch inside the ipa. Also you must sign the dylib otherwise it will be not loaded.
74 cp $PATCH Payload/Cartelera.app/Twitter 75 cp $LIB Payload/Twitter.app/libTest.dylib 76 /usr/bin/codesign -f -s "$CERTIFICATE" Payload/Twitter.app/libTest.dylib 77 /usr/bin/codesign -f -s "$CERTIFICATE" --resource-rules Payload/*.app/ResourceRules.plist Payload/*.app
Now is just time to try if everything is correct and working.
That methods not only allow to load a library inside a third app, although using the +load selector and swizzle you are able to modify the the application. The +load selector is a class method that executes when the library is load, so is a entry point for your swizzling.