I never really went to Subway (sandwiches) much, I’d been there maybe three times tops. One day I was at school and a friend of mine showed me a trick. He had something called a “secure” folder on his phone, if I recall correctly. This folder allowed him to drag apps in and out of it, making apps behave differently than they normally would. In this case dragging the subway app in allowed him to bypass the 24-hour timer for getting random coupons. Anytime he dislikes the coupon he got, he drags it out of the secure folder and back in and then he can just retry.
When he showed me this I noticed that something’s off.
When I saw my friend performing his trick with the secure folder I knew it was some kind of closed off environment where it’s as if the app is cleanly installed. The moment I realized this I knew the app wasn’t sound and I thought it’d be fun to see if we could reset the timer without the secure folder, which is way more convenient, and maybe find even more things to tamper with, like doing coupon selection.
The first thing we should do is download and install the app from the Google Play Store on an Android device and see how it works, I live in the Netherlands, so I’ll be using the Dutch version of the Subway app, everything from here on will apply to the Dutch version of the Android app and may or may not apply to other versions. Let’s start the app and see what it looks like!
It seems like we have to click on the start button and then shake the device to get the coupon. Let’s try doing that.
Alright, it seems like we got our coupon, the button below it says: “Hand in now”. So let’s try handing it in.
It seems we’ve almost handed it in, the strip at the bottom says we should swipe it away at the cash register. Let’s swipe!
So now it tells us we’ve used the coupon on the date of today. As you can see there’s no QR- or barcode to scan at the cash register. This means that they have no way to really verify if the coupon is real or not.
Now that we’ve established this we can move on to the next phase.
Plan of attack
Now that we’ve seen how the app operates we have to find out what we want to achieve. There were three things I wanted to achieve, which were:
- A way to select which coupon I want to use.
- A way to inject fake coupons.
- A way to reset the timer.
Now we’ve established what we want to achieve, we have to find out how we’re going to achieve this. To me it seems that the best way would be to reverse engineer the app to see how it operates under the hood and then write an Xposed module to alter the behaviour of the app to our needs.
So that’s what I went with.
The easiest way to reverse engineer an Android app is to decompile it back to Java. There are various tools which you can use for this, but I won’t be going into which tools I’ll be using.
After looking around in the decompiled code for a bit I found the mechanism which determines which coupon you’re going to get (only relevant parts shown and are deobfuscated).
ReadCoupons is reading 3 arrays from the resources, which are the name, the disclaimer and the ID of
the image of the coupon. However, note that the first thing it passes to it are the second argument, an
int, and it adds 1.
This is the index of the
Coupon, I’ve never seen it being used somewhere, but it’s there.
Then we have method
getRandomCoupon in class
d which just takes out such a
Coupon and returns it.
Note that the coupons are read from the resources and are not retrieved from a server, they’re hardcoded.
So if we’ll hook the
getRandomCoupon method in class
d and change the return value to another
Coupon we can choose
which coupon we want to use.
So now for the second goal, injecting a fake coupon. For this we should hook the
getAllCoupons method and add the coupons to the result of the method.
We can hook the constructor of the
Coupon class and we can create our own
Coupons using that.
So now for our third goal, resetting the timer. Instead of reverse engineering the app further I redeemed a coupon and decided to take a look at the shared preferences of the app.
coupon.xml sounds interesting enough, let’s take a look at the content of the file.
When I removed the string node from the map the timer was reset and I could just get another coupon.
A not-so-wise developer once said:
In the client we trust.
Like I said, we’re going to use the Xposed framework for this. I’ll be writing the Xposed module in Kotlin, because I enjoy the language and think it’s a better alternative to Java on Android. I won’t be going into the specifics of the Xposed framework or how to write an Xposed module, if there’s enough interest I’ll maybe write about that in the future.
The first thing which we need is a way to communicate from our selection screen (which still needs to be created) with the Subway app.
To me there’s one obvious way to go about this and that is to register a
BroadcastReceiver in the Subway app. This way we can actually
create a small API for ourselves inside of the Subway app, where we can request certain resources and make it do certain things. In our case
we need to be able to do a few things:
- Get all the available coupons.
- Set the current coupon.
- Reset coupon timer.
- Clear coupon selection.
The following code will do exactly that.
For those who don’t know,
use the Android IPC mechanism called
Intent is a message you can use to request something from another app component.
We will use the
Intents to send broadcasts, which are messages all apps on the device are able to receive.
You want to add (almost always) an
IntentFilter because you want
to receive only a select few
Intents you actually need.
So here we’ve registered a
BroadcastReceiver inside of the Subway app which exposes the aforementioned actions to us. Let’s break down the code from the top down
beginning from within the
So we’ll firstly dissect the
GETCOUPONS action. I think the name is quite semantic, it returns the currently available coupons.
We’re calling a static method within the Subway app, using reflection, to get an array of all currently available coupons. Because
we cannot access the coupon class directly we cast it to an array with generics. After this we iterate over the coupons and get the index and the name.
We then put this all in a
String with an easy-to-parse format and add it to the
Intent, then we send a broadcast which will be received at the other side.
As you can see we take an
Int out of the
coupon, if there’s none available we use
Int will represent the index of the array
which contains the currently available coupons.
Then we’ll check if the value is
> -1, this is because
-1 is our “undefined” value, so if it’s
-1 we shouldn’t do anything.
If the value is
> -1 I first call the
resetCoupon method, which resets the timer.
Like I said before, we want to inject fake coupons. We want to be able to select these and they will also be send just like any coupon via an
The way these fake coupons are going to work exactly is we’re going to photoshop an existing coupon and add it to our module’s resources. Then when we get an index,
we’ll check if the index is of a fake coupon. If we have an index of a fake coupon, we’ll use a technique called resource forwarding.
Resource forwarding is a way to replace an existing resource in the target app with another one. In our case we want to give all of our fake coupons the same
resource from inside of the app and then replace them on-demand i.e. we select on of our fake coupons in the selection screen and send the
Intent to the
then we’ll check to see if it’s one of the fake coupons and we detect that it is. Now we take the resource the fake coupon uses (unaltered from inside the target app) and we replace it with
another resource from our own module. Now the target app will show the resource from our module instead of the one it originally wanted to show.
Then I have an instance variable of type
Int? (this means a nullable integer in Kotlin)
to which I assign the value I retrieved from the
Intent. We’re not done yet, though. This is merely to supply the app with the information on which coupon to use. Now we need to make it
actually do it.
First we’re going to inject the fake coupons into the application.
Here you see an Xposed method hook, this enables you to alter the behaviour of a method. You can override the
afterHookedMethod, the former
will run your code before the hooked method is run and the latter after. In our case we want to alter the result of the method, so we only override the
As you can see we get a reference to the
Coupon’s constructor, then we get the array with the currently available coupons. Then we copy this array to a new array and make the size bigger
so our fake coupons can be added. Then we do a simple for-loop which uses the
Coupon’s constructor to create a fake
Coupon and put it in the newly created spaces inside of the
At the end we alter the result of the method to our newly created array.
Here we’re hooking the aforementioned method which determines which coupon you get.
couponIndex is not
null we try to get a handle to the current object we’re in, this should always be possible because we’re hooking an instance method.
Then we try to get the array with coupon objects from the current object we’re in right now, we index into it with the
couponIndex, which we retrieved through the
SETCOUPON action of the
Then we change the
result of the method, also known as the return-value, to the coupon object we got out of the array.
Now that we’ve implemented the
SETCOUPON action and the corrresponding hooks, let’s move on.
Alright, this is a very short and clear one. Whenever we get the
RESETCOUPON action we want to call the
This method gets the shared preferences which we were talking about during the reverse engineering phase. It just clears that shared preferences file, so now the times is reset.
An even shorter one, whenever we get the
CLEARCOUPON action we want to set the
null, this restores the default
behaviour of the app. I’ve never really used this one beyond testing, but it’s always nice to have a way to restore the default behaviour of the app.
The way the frontend works is pretty straightforwad, it will make use of the
BroadcastReceiver we’ve defined before and send
We’ll have a fancy selection screen with some buttons which’ll trigger these
Intents to be send with the right information, this is basic Android development
so I won’t be going deeper into this.
This is what the frontend looks like:
You can just select the coupon you prefer, click the upper button and it’ll launch the Subway app:
As you can see, we got the coupon we selected.
I think we’ve learned quite a bit. The takeaway from this is that the client is not to be trusted and I think you should always use a server to verify anything, without exception (if you care about your verification being legitimate).
Luckily, Subway fixed these huge issues by deprecating this app and releasing a new app called Subcard.
If anyone’s interested I can open source the code for the Xposed module and add some more comments. There aren’t a lot of resources on Xposed out there and I can imagine it could be helpful to some.
Edit 29-10-2018: I open sourced the Xposed module!