How I discovered Instagram's upcoming video calling feature on iOS
Yesterday I managed to grab some screenshots of a video calling UI on Instagram. This is the story of how that happened. I think the description of the process will be pretty useful to anyone who’s interested in reverse engineering to find hidden features.
It always starts with a rumor or some other find. Yesterday, we published a story on 9to5mac about someone having found assets on Instagram’s Android app related to making video calls, a feature that Instagram doesn’t currently offer. That grabbed my attention and I wondered if I could find the same thing on iOS.
Note: my tweet about the feature on iOS that resulted from the process described below is now embedded in the 9to5mac post, keep in mind that it started as an Android-only rumor.
The first thing I did was to use iMazing to download Instagram’s IPA from the App Store. As you might already know, an IPA file is just a zip file, so to extract its contents all you have to do is change the extension from .ipa to .zip. I looked inside the Instagram.app bundle for any assets that could indicate a video calling feature. This can be done with Spotlight or in Terminal with the find command. Naturally, I looked for any files with “videocall” or “videochat” in the name, but I couldn’t find anything. Then I used Asset Catalog Tinkerer to open the Assets.car file and look in there, nothing.
Since the search for assets didn’t prove fruitful, it was time to look into the binary. We’re talking about an App Store app, so the first step is to decrypt the binary. Unlike Apple’s builtin apps, binaries for apps downloaded from the App Store are encrypted and can only be decrypted while running on an iOS device, a process that’s only doable with jailbreak. So I got my handy jailbroken iPhone 5S, connected to it and proceeded to decrypt the app.
The process to decrypt a binary on a jailbroken device goes like this:
- SSH into the jailbroken device
- Run the target app
Find the PID for the target app:
Use bfinject to inject a decryption dylib into the target process and wait for the process to finish:
After that, I used netcat to download the decrypted IPA on my Mac:
Then I renamed it to .zip and extracted Instagram.app from the Payload folder:
Okay, so now I had a decrypted version of Instagram, woot!
I usually start by loading the binary into Hopper, but Instagram’s executable is a whopping 50mb (!) so I knew it’d take a very long time to parse and disassemble. Given the imense size of the binary, I decided to do a strings search first, and then follow up on whatever I found. Remember that at this point, I wasn’t sure I’d even find anything, so it wouldn’t make sense to spend all the time (and CPU power) disassembling the binary only to find nothing.
Extracting the strings from a binary goes like this:
This is only viable in a decrypted binary, since the encrypted version will only provide meaningless garbage.
Notice I piped the output of the strings command to sort and then output that to a file named Instagram_34.txt. I do that because it makes it easier to diff between versions later if I want to. Sorting means the two files being compared will be more stable, which leads to a more useful diff. Versioning the file name means I always know which version the strings came from. This was not necessary in this instance because I’m not doing any comparison, but can prove useful in the future when Instagram releases a new version and I decide to check what’s changed.
With the strings file ready, it’s time to search it for something that can indicate a video calling feature. Grepping for “videocall” revealed this:
That’s just an excerpt, there are tons of other “videocall” strings in there. So now I knew they’re definitely working on some sort of video calling feature.
I could stop here, tweet this and go to sleep. But what if I could actually run that video calling code to see it in action? 🤔
Running the code
To run arbitrary code in an app I don’t control, I could just attach a debugger and go from there, but that’s not fun, so I decided to use Cycript!
Bfinject comes with Cycript builtin:
Now I could connect the Crycript console on my Mac to cycript running inside Instagram, on my iPhone 5S, ain’t that cool?!
As you can see, once I have the Cycript console connected to Instagram on my iPhone, I’m able to run ObjC code and do whatever I want. But how did I know which code to run?
Instagram, like most iOS apps, is written in Objective-C. This means you can dump a header of its classes by running class-dump on the decrypted binary:
Then I opened the Instagram.h file in TextWrangler and looked for “videocall” again, until I found something I thought I could use:
The method name `presentVideoCallViewControllerWithSessionId:` sounds promising. It’s implemented in a `UIViewController` subclass called `IGDirectThreadViewController`. Judging by its name, I guessed it was the view controller responsible for the direct message view, when you’re chatting with someone. So I opened that screen in the app:
Then I traversed the view controller hierarchy to find the active instance of `IGDirectThreadViewController`. For some reason, Cycript’s handy choose() function didn’t work for me.
And then I called the `presentVideoCallViewControllerWithSessionId:` method. It takes a parameter (a session ID), I guessed it’s a string and just passed an empty one, to see what would happen:
The feature didn’t actually work, but at least I could grab a screenshot that proves it’s a work in progress. This happens very frequently when dealing with unannounced and/or unreleased hidden features: you manage to activate the feature, but it doesn’t really work.
I hope this post provides some useful insight into how I approach looking for unreleased features in iOS sofware, if you like what I do and would like to see more content like this, you can support me on Patreon. Make sure to follow my posts on 9to5mac as well.