iOS Game Hacking: Minesweeper
I recently saw a tweet by Osanda Malith about hacking Minesweeper on Windows, which got me motivated to do something similar for iOS. Since I had never won a game of Minesweeper before, I decided that cheating at it was the way to go. This post will cover how I reverse engineered a Minesweeper app for iOS and will delve into the various technical aspects of iOS reverse engineering on both a Jailbroken and non-Jailbroken iPhone. So if you’re interested in reverse engineering or game hacking, read on!
The app I targeted is Minesweeper Classic 2. The devices used were a jailbroken iPhone 5S running iOS 10.2 and a non-jailbroken iPhone 7 running iOS 11.2.1. All the companion code for this post can be found here.
Static Analysis
I downloaded the application on a jailbroken iPhone running iOS 10.2 and retrieved the decrypted ipa
using Clutch. With, the decrypted binary, further analysis can be done using a disassembler such as IDA Pro.
-[MinesweeperViewController startNewGame]
is the function responsible for initialising the game. The MinesweeperViewController
has a member named board
which returns a BoardView
which stores the game grid as a 2D integer array. startNewGame
initialises this grid using -[BoardView setGrid: x: y: ]
. All cells of the 2D array which hold bombs are initialised with the number 17
and the rest are set to 16
.
Accordingly, all we have to do now is select the cells in the grid which have the value 16, and avoid all the cells with the value 17. It would be a great aid if we could somehow highlight all the cells with bombs.
The -[BoardView setGrid: x: y: ]
is responsible for highlighting the cell at index (x,y) with the appropriate image.
Usage of this function by the app can be traced with Frida which is an amazing dynamic instrumentation toolkit.
This is the trace of setGrid:x:y:
being called on various events including displaying a flag, a number and a bomb. So this function can be utilised to highlight all cells which have bombs present by checking the grid.
Great, so to accomplish this, there are various options. I’ll be describing 3 approaches which can be used, namely
Dynamic Instrumentation
In this post, I’ll be using Frida to accomplish this task. To highlight the cells containing bombs, we first need to get a handle to the active MinesweeperViewController
instance. This can be achieved by getting the rootViewController
which happens to be the active instance of MinesweeperViewController
. The following snippet demonstrates how this can be carried out with Frida.
The MinesweeperViewController
has multiple members associated with it including minecount
, board
, flagcount
, zoomscale
and many more. The board
function is useful to us because it returns the BoardView
associated with the MinesweeperViewController
instance, essentialy giving us access to to the game grid.
Now, all we have to do is iterate over the game grid and highlight all cells with value 17. To retrieve the value of a cell, the -[BoardView gridAtx: y: ]
can be used which returns the value of the cell present at index (x,y).
Now we have all the primitives for sucessfully marking the bombs. One caveat about using the -[Board setGrid: x: y;]
function is that, since this function is changing the UI of the application, to reflect all changes without any delay, we have to call this function from the main thread. This can be done by scheduling the function with GCD (Grand Central Dispatch).
This sums up the steps. The complete Frida script:
And here it is in action.
One further optional improvement can be made here. Instead of calling our function init
and findBombs
manually from the script, we can replace the implementation of a built-in function of the app. A viable candidate for this is the -[MinesweeperViewController toggleFlag]
function which is called when the Flag
button present in the upper right corner of the game is pressed. This method of switching or replacing function implementations is called method swizzling.
I’d like to digress a little over here and mention a small note about method swizzling.
+----------+
| Class A |
+----------+
+---------+ +---------+
|SELECTOR | |SELECTOR |
|-s1 | |-s2 |
+---------+ +---------+
+ +
| |
v v
+---------------+ +---------------+
|IMPLEMENTATION | |IMPLEMENTATION |
| -s1 | | -s2 |
+---------------+ +---------------+
Each method has a selector and a corresponding implementation. Method swizzling switches or replaces the implementation. For example, we can switch the implementation of s1 and s2 like so.
+----------+
| Class A |
+----------+
+---------+ +---------+
|SELECTOR | |SELECTOR |
|-s1 | |-s2 |
+---------+ +---------+
+ +
| |
v v
+---------------+ +---------------+
|IMPLEMENTATION | |IMPLEMENTATION |
| -s2 | | -s1 |
+---------------+ +---------------+
So now, when the selector s1
of class A
is used on an instance of A
, the implementation corresponds to s2
and vice-versa. This is possible because the Objective-C runtime supports dispatching methods at runtime and provides APIs to get and set the method implementations.
There are many detailed articles about method swizzling on the internet if you would like to read about it in detail.
Coming back to our addition, we can change the implementation of -[MinesweeperViewController toggleFlag]
using Frida like so.
This concludes our first technique. Now we’ll look at another implementation of the same hack using a Theos tweak.
Theos Tweak
This technique will only work on Jailbroken devices. This technique achieves code injection by leveraging the powerful Cydia Substrate platform. Tweaks can be written in Logos which is a simplified version of Objective-C.
For this approch, we will replace -[MinesweeperViewController toggleFlag]
’s implementation with our code to iterate over the game grid and populate all bomb cells with flags.
Since we are hooking a custom ViewController which is not available by default, we will have to make a header file for our tweak so that it won’t complain about the functions we will use.
The tweak code is fairly straightforward and achieves the same purpose as the previous method.
Behind the scenes, hooking is carried out using the MSHookMessageEx
API provided by Cydia Substrate. This is basically a high-level wrapper for performing method swizzling.
Some of the requisites for building the tweak using Theos include specifying the bundle identifier of the app which in our case is com.libertyforone.minesweeperclassic2
.
Once the tweak has been built and deployed to the iPhone, we can view it and Cydia and see that it works as expected.
This concludes the second technique and now we’ll finally look at a way how all this can be accomplished for a non-jailbroken device.
dylib Injection
This technique works for jailed devices but requires modifying the binary. In this technique, we will make a Cocoa Touch Framework which will be used to perform method swizzling similar to the last technique but using Apple’s API.
If we were performing method swizzling on a method present in one of Apple’s standard libraries such as UIKit, CoreFoundation, CoreLocation, etc. we could have simply achieved this by writing a Category
which simply put is, a way of extending a class’ functionality and is a standard way to perform method swizzling. Unfortunately in our case, we need to swizzle a method present in non-standard custom class, namely MinesweeperViewController
.
We will have to use Apple’s Objective-C Runtime API for achieving our goal.
First we will create a custom class with our implementation which will replace toggleFlag
. The header file looks like:
The corresponding implementation is:
This functionality is identical to the previous examples. Now that we have this out of the way, we will perform method swizzling by finding our class MinesweeperViewController
, in memory and replacing -[MinesweeperViewController toggleFlag]
’s implementation with our implementation.
According to Apple’s documentation, func objc_getClass(_ name: UnsafePointer<Int8>) -> Any!
takes the name of the class to look up and returns the Class object for the named class, or nil
if the class is not registered with the Objective-C runtime.
func class_getInstanceMethod(_ cls: AnyClass?, _ name: Selector) -> Method?
returns the method that corresponds to the implementation of the selector specified by aSelector
for the class specified by aClass
, or NULL
if the specified class or its superclasses do not contain an instance method with the specified selector.
func method_exchangeImplementations(_ m1: Method, _ m2: Method)
exchanges the implementation of two methods.
In our scenario, these are all the function’s we require for method swizzling.
The following code snippet is responsible for replacing the implementation of -[MinesweeperViewController toggleFlag]
with our implementation -[CustomClass toggleFlag]
.
Since we’ve used static void __attribute__((constructor))
modifier for the init
function, this will be called when the loader
class is loaded in memory which is a suitable entry-point for code injection.
Once the framework has been built using Xcode, we can get the generated dylib from the built Framework and package it with our app.
We’ll unzip the decrypted ipa which we obtained via Clutch detailed in the first method, and copy over our dylib into a new folder inside the Payload folder of the app. Now for instructing the application to load our dylib, we have to modify the app binary. iOS binaries are of the Mach-O file format and we’ll be modifying the load commands section using a tool called optool.
This inserts an LC_LOAD_DYLIB command into the load section of the binary and increments the Mach header’s ncmds
variable.
Now we can repackage and sign the ipa
using a Developer Profile and install the ipa
onto a device using Cydia Impactor.
Note that these techniques work for all difficulty levels in the game and for any number of mines!
Well this finally concludes this post. The techniques mentioned in this post can be applied to various different scenarios leading to some interesting and fun results. Therefore, I encourage you to dwell deeper and hack some games of your own! I hope you enjoyed reading this post and learnt something new. Feel free to leave comments for questions or any corrections!