Just like any other student in a dorm, I often go out to eat because I'm too lazy to cook something and do the dishes after. The only thing that sucks about this is the price. So.. what if I could use my skills once more to take advantage of the McDonalds Mobile Application to reduce the costs of my food?
The McDonalds Application
McDonald's has a mobile application for both Android and ios phones. It turns out that there are multiple versions of the application, and it's based on your country. Since I am from Belgium, I will be looking into both the Android and iOS version of the application. The Android one is made with Java. I am not a javatar but it is way easier to reverse engineer Java compared to C/C++ (swift) based application.
The McDonalds' application has a coupon system, where you have to scan a QR code before you place an order in one of the restaurants. After completing the order, you get points added to your account balance. These points are called "Loyalty Points", and you get about 10 points for €1 spent at the McDonalds restaurant.
My goal here is to share my QR code with my friends, they think the loyalty points system is stupid so they don't use it at all. It sounds like free points to me if I can hand out my QR code to them, so I get the reward. After using the app I noticed that the QR code changes every time you open it, and then after X time, the QR code changes again. This makes it pretty hard to share the QR code with my friends when they need it. Oh, and it also seems like the app is blocking screen recording and screenshots, which makes it almost impossible to share QR codes. My idea was to find out how the QR code gets generated and then reproduce this process.
Reverse Engineering (Network)
Before we do anything at all, let me scan the QR code to see what data is held. After doing this it turns out to be a number with 4 to 6 digits, meaning it's a value from 1000 - 999999. We now know that we are looking for this value. Its time to fire up mitmproxy and configure my iPhone to use the Proxy IP of my laptop.
While my phone is starting the McDonald app, I can see my laptop screen getting filled with requests. Surprisingly there is nothing useful at a first glance. None of the HTTP/HTTPS URLs look like they came from the McDonalds app.
Some of the URLs should come from the McDonald app since the only thing I did on my phone was open the app. After having a look at each URL, I figured out that the "dif.gmal.app" is the DNS used by the app. Let's highlight those URLs to have a better look at them.
Have a look at that, you can now guess that it registers my device with the URL containing device:register and that it's then getting some configuration and account info (consumers info). I took a look at the URLs, what they request, and what they get. There is not much useful information in there, but I'm still gonna show you whats in the v1/con/v3/consumers/me/tagvalues request & response.
Starting with the GET, the response JSON body looks like this:
Having a laugh? Well, at least I am. It seems like some 'developer' forgot to remove the example parameters from a tutorial he copies & pasted. Kind of funny, isn't it?
Alright, let us take a look at the POST request:
Those two values are sent to the server.. right after we received a list of values that also contained these two values. Do you see what's going on here? Maybe we could also add "Test_Group_B" and "Test_Group_B" to the JSON array and send it with a POST? Let's try it out! Who knows what kind of weird debugging stuff we will see in our app.
Uh, oh! Looks like we are unauthorized to do so? The message says that 'Digest verification' has failed. Seems like there is some kind of signature located in the header with the name x-dif-authorization. The signature for the successful call looks like this MEQCIGCUUK19BieShlyezunZ.., it might look a bit like Base64 but it isn't... Good job McDonald's, I will have to stop my curiosity here and continue with my main goal.
Alright, back on track, heading for the QR code!
While I'm watching the mitmproxy screen, I use my phone to navigate to the section where the QR code is visible inside the app. After I opened the app, I see my screen filling up with requests, and they look promising!
The following three highlighted URLs are what I'm digging for. The first two v1/off/v3/offers?.. and v1/off/v3/loyaltycards?.. contains all the information about the current offers. Offers can be coupons, so it's a good thing to have a URL to get all the information of them (such as ID, expire date, price, etc..).
The third URL is our jackpot! the URL is ../v1/con/v3/consumers/me/verificationtoken.. and the response looks like this:
Now, remember that we couldn't send the tag POST stuff? that was only because we edited the JSON body content, resulting in having an invalid signature in our headers. For this URL we don't need to change anything at all to make it work, we can keep our current headers and request as many codes as we want!
The last thing we gotta do is put this all together in a nice C# application, host it somewhere on my website (www.mcdo.ferib.dev) and share the website with my friends so I can get their loyalty points!
Reverse Engineering (Application)
At this point, I have everything I need to achieve my goal. But oh boy, my curiosity is out there, wondering what the Android application looks like. Since the Android application is way easier to reverse engineer compared to ios, I will take a quick look at it. We might be able to find out how the Signature in the headers is calculated, this means we would be capable of sending custom-crafted HTTP(s) request to the server.
But it turns out that some of the Java code is obfuscated. I used an online Java decompiling tool to convert the APK into code, after having a quick look I already notice most of the code looks like garbage to me. I'm not a javatar and won't spend time restoring the code. Some packages aren't affected by the obfuscation, I found a package called "mcdonalds". The package mainly contains view models, no actual programming logic to see here. Let's look at a few view models then.
At the screenshot above we see that there are two card types defined, STAMP_CARRD and POINT_CARD, good to know that there are multiple versions of the card, but I don't see any use for that, those loyalty cards are based on what country you are in, the type won't change anytime soon.
This 'rootchecker' grabbed my attention. It's known that the application won't function on a rooted phone. According to McDonald's its for 'safety' reasons that they don't want their application to run on rooted phones. For those who have a rooted phone and want to run the application, just patch the above function and you should be good to go.
I have a jailbroken iPhone XS, so I'm not even gonna bother patching stuff in an APK. Once again, Good job McDonald's, I'm gonna stop looking at the APK and just finish writing my C# application. At the moment I have everything I need to achieve my goal.
The result is my QR code (which is plain text ;_;) hosted on my website, the QR code will get refreshed every minute. That's it for now, I thought about adding Discords Oauth API to the webpage so I can keep track who got which code. It is possible to get the history of the points gained, but it's not possible to find out from which QR code it is. Disappointing, otherwise I'd be able to reward my friends somehow, or at least keep track of who sent how many points.
The above iframe shows my current QR code, spend €5 at McDonald's using my code so i can have a free coffee!
The project can be found here on GitHub.
What if I told you, the title isn't a clickbait and I was having free food the whole time?
While I made this project, I thought 'what if I just generated as many codes as possible?' and so I did. I deployed an AWS server and executed my code on there, here is how it looks
At first, I had no idea if it would work, so I left the tool running at night. The next day I woke up, logged into my McDonald account and saw 1000 points on my account, what a surprise!
The next thing I did was finding out how to buy a coupon, after fiddling with that I was able to automatically buy coupons without activating them, so I could stack them as much as I want. My app looks like this now:
As of 10/03/2020, McDonald has updated its server and it is no longer possible to do this many API requests, which means that this exploit is no longer possible.