iOS apps RE

Reverse iOS apps for educational purpose.

Bypass In-app purchases of react native iOS app

Written on March 23, 2020

Today we will reverse engineering a react native iOS app and bypass in-app purchase to use locked features. Below is an example of purchase screen whenever tap on premium content. purchase screen
Figure 1: Sample In-app purchases locked contents

Disclaimer

This post is for educational purposes only. How you use this information is your responsibility. I will not be held accountable for any illegal activities, so please use it at your discretion and contact the app’s author if you find issues. We will inspect an app has In-app purchases feature, name REDACTED. The figures during the post just for demonstrations, might not relevant to REDACTED app.

Prerequisites

Below tools are used during this post:

Overview

After installing and launching REDACTED app on the device, it shows me the main screen which some navigation items on the tab-bar menu. Select one of the items, let say Tools lead me to a new screen which allows me to select tools app supports. Select either tool will navigate to a new screen that allows us to see that tool for a second followed by a purchase screen.

The only option we have is to subscribe and do In-app purchases, otherwise, click X button to cancel purchase will pop us back to Tools screen again. We nearly can use that tool but it’s blocked in front of our eyes. However, this seems to be client-side validation, if we can find out where is logic to show purchase screen and disable that, we can get rid of it and use the tool for free. Let’s do it ^_^

Static Analysis

First of all, let’s start with UI. As usual, let trace from the text we can see on the Purchase screen, something like “other great features with Pro access”. So let do some static analysis by extracting the app .ipa file and looking for that screen. With the help of Frida iOS Dump or CrackerXI, we can easily pull out .ipa file of REDACTED app on a jailbroken device, unzip .ipa and navigate to Payload/REDACTED.app folder. All of the app resources and binary files are inside this folder. Normally app strings are stored in some below places:

  • Localization files (.lproj/.strings)
  • Property List *.plist
  • Hard-coded in Mach-O file (binary file)
  • json/text files…

With simple search command grep -iRl "other great features with Pro access" ./Payload/ luckily we found this text is contained in main.jsbundle file. Find files containing specific text
Figure 2: Find files containing specific text

React Native

If you are a mobile app developer, you can confirm that this app was developed (fully/partially) using React Native framework. React Native is an open-source mobile application framework created by Facebook. It is used to develop applications for iOS, Android, Web by enabling developers to use React along with native platform capabilities. And the main logic of the app remains inside main.jsbundle which used to be minified javascript file.

main.jsbundle
Figure 3: main.jsbundle content

This file size is around 5MB, it’s huge and looks like the whole application logic is inside this single file, but scare not!! Beautifier comes to rescue ^_^

Reverse main.jsbundle

Copy main.jsbundle content and paste on Beautifier site we will get formatted one. It won’t decrypt the file to original source code but it formats syntax for readability, which we can use to reverse the app logic.

formatted main.jsbundle
Figure 4: Formatted main.jsbundle

Let’s open original main.jsbundle and paste formatted content into any text editor, in my case I’m using Sublime and search for the text “other great features with Pro access”

Now searching for something like “purchase”, we will see that we can find some results, one of them likes below is worth to pay attention and dive deep. onPurchaseFinish
Figure 5: Purchase search leads us to onPurchaseFinish callback

Do you see what I see? hasProAccount rings the bell? By searching “purchase” we found a new flag hasProAccount which is a boolean type and might be used to decide if the user has a free account or pro account. Let’s note down this variable and searching all of its references to see how it’s used. But before that, let’s try to understand what this method is doing. It will be hard to read minified javascript code as it uses comma as the end of statements and some if/else syntax not clearly to observe. To make our life easier, I found this JS Nice tools helpful. It’s statistical rename, type inference and deobfuscation, perfectly fit for our case so let give it a try js nice
Figure 6: JS Nice comes to rescue

The deobfuscated result is awesome. We can see there is a bunch of if-else logic and it’s much easier to read and understand the function now. This method will be triggered when we tap on any features in Tools tabs (CHROMATIC_TUNER_KEY, METRONOME_KEY, BRAIN_TUNER_KEY, CHORD_LIBRARY_KEY). It will do nothing if hasProAccount is true. But if hasProAccount is false, tap on any above features will do 2 things:

  1. Log an event with the name BannerUpgradeView. The name is self-explained this would be the purchase view we see in Figure 1
  2. Show some kinds of marketing screen (which probably the purchase screen)

If hasProAccount is false and tab in features that do not belong to the above list, it will trigger that event as normal. So from here we can make sure hasProAccount will be the key to unlock In-app purchases. Let’s search hasProAccount = to find out where this value is set, the results are only 3 places. hasProAccount =
Figure 7: Search where hasProAccount is set

Patch the app

Now the job is trivial by replacing hasProAccount = true for all 3 places:

  1. g.hasProAccount = s => g.hasProAccount = true
  2. g.hasProAccount = u => g.hasProAccount = true
  3. g.hasProAccount = !1 => g.hasProAccount = true

Then copy modified main.jsbundle to overwrite the one in the jailbroken device by below command: scp main.jsbundle root@iphone_ip:/private/var/containers/Bundle/Application/REDACTED_UUID/REDACTED.app/main.jsbundle Relaunch the app, BOOM!!! No more in-app purchases screen, even ads is gone too, so one stone two birds yeah!!!

Final thought

  • The client-side should show a purchase screen first instead of a content screen followed by. This would hide the feeling that premium content already been on the client but blocked by another purchase screen, try to get rid of purchase screen means can use the app.
  • React native is fast to develop applications for cross-platforms, but as we can see client-side logic is in plain text and with some steps required we can reverse and unlock client-side logic.
  • File Integrity Checks can be used to mitigate main.jsbundle modification

>> I hope you find this post helpful. Please follow and connect me on Twitter to be notified on my upcoming posts.