Saturday, September 10, 2011

Reverse-engineering iOS apps through console output and logging

One useful practice in software development is the use of logging, to record details about the status of the app, or other events of interest at runtime. This can be a life saver when problems occur and they need debugging, either live or post-mortem. The logs are also useful during the development of the app, as a way to quickly asses its sanity, at a glance, as opposed to placing breakpoints and using the debugger to inspect the value of variables of interest. In some instances, where timing issues could affect the behaviour of the app, stopping the execution with breakpoints is infeasible, possibly resulting in heisenbugs. In such instances logging can be a better alternative. So you'll find logging used heavily on all types of apps.

In iOS we have this function, part of Foundation framework:


Logs an error message to the Apple System Log facility.

void NSLog (
   NSString *format,
Its use is encouraged in many introductory books and tutorials for iOS programming. By default this function outputs content to the device console available through the Organizer view of Xcode. Now, while this is all good for debugging apps that we're developing, I was very surprised to discover output produced by 3rd parties apps, that were installed straight from App Store, in this case "Globe News". I obtained this output by simply running the app on my iPad set up for development, connected to my laptop running Xcode:

In many cases such output may contain sensitive information, like details of various web services endpoints used by the app, or other information that could be used for reverse engineering. This may well be a bug in this particular combination of Xcode/iOS versions, but as developers we need a way to guard against this.

Another concern is the overhead incurred by having our code peppered with such NSLog statements. Each time such a statement is executed, depending on how efficient its internal implementation is, its input parameters may be processed and a string may get formatted, even if there isn't an output sink available. And on mobile devices, like those powered by iOS, where every wasted CPU cycle translates in battery drain, this is of particular concern.

A popular way to control logging is to enable it only in debug builds, by wrapping the calls to logging functions in some macros. For iOS we could use this setup:

#ifdef DEBUG
#    define DLog(...) NSLog(__VA_ARGS__)
#    define DLog(...) /* */
#define ALog(...) NSLog(__VA_ARGS__)

This snippet can be placed in the _Prefix.pch file, that makes the macro available in all source files. Using this macro enables logging only on debug builds, hence getting rid of all problems mentioned above. Your released app, available in the App Store wouldn't even have any logging compiled in, hence it would be better protected against reverse engineering.

No comments: