iOS Security: Reverse Engineering Messenger's Chat Bubbles
This article is SwiftRocks's slimmed down version of my original 2016 Medium article. The Medium article is badly written and contains mistakes, but it has more information on how to reproduce each step.
Update: As of May 3, 2018, this sadly doesn't seem to work anymore. My web chats returned to the blue color and the custom changes only apply locally. I guess color changes are validated on the backend now. Damn you Zuckerberg!
Due do the messaging nature of the Objective-C runtime, a great amount of information about your code can be extracted and manipulated during runtime by external tools. The Selectors you reference everyday are nothing more than exposed strings in your code, despite what Swift's #selector abstraction might imply. With the right tools, any production app can be messed with just like it's Xcode debugging counterpart.
Facebook's Messenger app is an interesting app to test this kind of concept. It contains many minigames and features that change the app's layout globally - as in, they are not client side changes, everyone can see what you do both in the app and the website. These features contain zero security measures, after all, it's not like you can cause damage with them. That makes them great targets for learning iOS security concepts.
The feature I'll inspect in this article is the chat bubble color selector. It allows you to change the color of a chat (duh) to a few pre-determined colors. But what if I want to use my own custom color instead?
Cycript is a debugger with a twist: you can easily print, create and call Objective-C methods without any of the complexity or pain you might experience while using gdb or lldb. Combined with a jailbroken iPad with OpenSSH installed, you can treat app like it's source code was available right in front of you.
ssh root@192.168.1.103
cycript -p Messenger
var root = [UIApplication sharedApplication].keyWindow.rootViewController
MNModalHostViewController
The iPad currently has the color selection action sheet opened. If Facebook's engineers are good with naming conventions, this means that following down root's hierarchy will eventually lead us to a view whose's name might contain some combination of the the words "Color", "Selector", "Chat" and "Bubble". In Cycript, you can see a view's properties by putting a * before it.
The second item of the childViewControllers array is a MNActionSheetViewController. The color selection screen is the only thing opened on the iPad, so that must be it.
The actionSheet's (created with var actionSheet = root.childViewControllers[1]) childViewController is a MNThreadCustomizationPickerViewController. No idea what it means, but the name is promising.
The PickerViewController contains an internal pickerView, as expected. There are two here, but (spoilers) the right one is the FBPickerView.
The pickerView contains a lookup table with 15 elements (we have 15 colors), which is how I suppose they know which colors to show in the action sheet. It also contains a collectionView. In this case, the easier way to manipulate the colors ended up accessing the cells directly.
As expected, the collectionView contains 15 cells.
Here, I picked a random cell (calling it blueCell). It seems that each cell has a button (FBPickerViewButton) property.
And each FBPickerViewButton contains an item (FBPickerViewItem)
Unfortunately, it turns out that a FBPickerViewItem's properties don't contain anything useful involving chat colors. The actual color information ended up being in it's init method.
Let's pretend I didn't know that. To print a class's methods, we can use of the following snippet:
function printMethods(className) {
var count = new new Type(“I”);
var methodsArray = [];
for(var i = 0; i < *count; i++) {
var method = methods[i];
methodsArray.push({selector:method_getName(method)});
}
free(methods);
return methodsArray;
}
The result of calling printMethods("FBPickerViewItem"). There it is!
In order to intercept the initialization of this class, we can use Method Swizzling. Cydia Substrate can be installed on a jailbroken device to provide helpers specific for this purpose.
@import com.saurik.substrate.MS
var _setColor_pointer = {};
MS.hookMessage(FBPickerItem, @selector(initWithColor:accessibilityTitle:accessibilityHint:isSelected:isSelectable:), function(arg0) {
return _setColor_pointer->call(this,[UIColor blackColor],”a”,”b”,false,true);
}, _setColor_pointer);
Basically, our swizzled method calls the original implementation, but with other arguments. In this case, I force the color to be black. If I close the action sheet and open it again, the result is...
The colors changed, but will it actually work?
It works locally, but will it work if I open Messenger somewhere else?
Yes! If they have analytic events for these colors, someone at Facebook will be very confused trying to figure out why there's some random guy with a black chat.
Hopefully this can give you an idea of how easily your app's data can be tampered it. Be careful with what's inside your binary - no matter how hard you try to hide it, someone who tries hard enough will eventually find it - specially if they are being paid to screw you up. Do not try to reinvent the wheel, use actual, market proven security measures.