The React Native Pentesting for Android Security Masterclass has taught us how to reverse engineer react native applications by now, so we’ll explore both methods for decompiling APK files and analyzing their structure.
In React Native applications, the “index.android.bundle” file plays a crucial role. It contains all the app’s compiled JavaScript code in a single minified file. This file is generated when the application is built and includes everything from the main components to the various libraries and dependencies used in the app.
As a result, this file can become a significant security concern because it may contain sensitive hardcoded information such as credentials, API keys, or URLs that attackers can easily extract if they decompile the app. Understanding what kind of sensitive information can be stored in this file and how to analyze it is essential for securing React Native apps.
What is the “index.android.bundle” file?
/<appfolder>/assets/
There is a lot of sensitive information that might be hardcoded in the application component files, which are later compiled into the “index.android.bundle” file. We will look at some of the information that we can find in this file.
Poor management of credentials and tokens is a naïve mistake in many Android applications, and the React Native application is no exception. In fact, hardcoding is much higher in React Native than in regular native Java apps, and “index.android.bundle” is a goldmine for hardcoding.
You can search for keywords such as “secrets, tokens, password, apikey, username, login” etc. to find such goofy hidden secrets in the “index.android.bundle” file.
Most React Native applications store information in third-party databases such as Firebase. Numerous instances of hardcoded credentials for these third-party databases have occurred, and many credentials are too permissive within the React Native application.
The following keywords can be used to grab these credentials within the target React Native application:
Developers tend to add backdoors or URLs in the code for various purposes, such as debugging, shortcuts to the functionality for convenience, etc. Sometimes, they forget to remove those URLs and shortcuts while deploying the code to production. We can scratch through the file to find these hidden URLs and shortcuts.
The “index.android.bundle” file contains the application’s core code, which can sometimes be huge to analyze. Depending on the type of application, technology, frameworks used in the application, etc., we can be more creative with custom keywords.
As we saw above, all JS code is crunched into one “index.android.bundle” file. Navigating through this bulk code is a headache. Fortunately, there is a way to break down this bundle code into multiple JS files with the help of the following npm module.
1. Install the “react-native-decompiler” module. The installation instructions are in the module URL above.
2. Unzip the contents of the vulnerable application into a folder and go to the “assets” folder.
3. Now open the command prompt in “assets” folder and type the following command:
npx react-native-decompiler -i ./index.android.bundle -o ./output
4. Wait for the process to complete. The “index.android.bundle” file will be decompiled into multiple JS modules in the “output” folder.
5. Unfortunately, unlike React JS web applications, most React Native Android applications do not generate a source map file. Thus, we have to manually navigate through various application components.
You got decompiled JS files, but things are still messy. Let’s simplify things. We will navigate through these files to reach the right code.
1. Once you decompile the “index.android.bundle” file, you will see multiple .js files in the “output” folder. We can start with the “0.js” file. Consider this file as the main component file of the application. (App.js)
2. Open this file and check which files are imported at the beginning of the file. It should look like the following:
or in case of multiple imported components:
3. We can spot these files from the list of multiple js files.
4. Open this file, and you will see the bundled JS code (bundled via webpack).
5. We will try to make more sense of this stuff in the dynamic exploitation section.
As we saw above, the “index.android.bundle” file contains the core logic of the entire application. Thus, the React Native team created their own JavaScript engine called Hermes. This engine runs React Native applications. The JS source code is often compiled into the Hermes bytecode, somewhat obstructing the JS code.
Hermes is an open-source JavaScript engine optimized for React Native. For many apps, enabling Hermes will result in improved start-up time, decreased memory usage, and smaller app size. Refer: https://reactnative.dev/docs/hermes
Thus, when you decompile the React Native application that used Hermes during compilation, the code in the file “index.android.bundle” will be converted into Hermes byte. The contents of the file will look like this:
Fortunately, there is a way to convert this mess into a human-readable format. Shoutout to *https://github.com/bongtrop* for creating hbctool. This tool lets us disassemble the encrypted bundle files back to the Hermes instruction set, which is in human-readable bytecode.
hbctool: https://github.com/bongtrop/hbctool
Challenge APK: https://github.com/ErbaZZ/hermes-reversing-lab/blob/main/HermesReversingLab.apk
Steps:
1. Decompile APK and go to the “/assets” folder.
2. There you will find the “index.android.bundle” file.
3. Install the hbctool with the following command
pip install hbctool
4. Open the command terminal in the “/assets” folder and type the following command to disassemble Hermes bytecode into human-readable format.
hbctool disasm <index.android.bundle> <output_folder_name>
5. A folder will be created containing disassembled Hermes bytecode. Now go to the output folder (dis_code)
6. Open the “instructions.hasm” file and analyze instructions sets.
7. You can find secrets stored in String constants by searching for specific keywords such as password, tokens, secret, apikey, etc.
8. You can use keyword: Oper[1]: String( to grep all of the strings in the bytecode.
Hermes is a custom JavaScript engine created by Facebook. Therefore, the only way to understand this bytecode is to analyze the code patterns.
What is AsyncStorage in React Native?
According to the official React native document,
AsyncStorage is an unencrypted, asynchronous, persistent, key-value storage system that is global to the app. It should be used instead of LocalStorage.
AsyncStorage is also asynchronous, i.e. its methods run concurrently with your code It is also persistent, meaning that the stored data will always be available globally even if you log out or restart the application.
The data stored via AsyncStorage is unencrypted; thus, it is accessible to anyone with access to the device who can get it in cleartext. If the application stores any credentials of services, user’s session token, passwords, or any other sensitive information via AsyncStorage, then it is easy for an attacker with access to the device to access this data.
Where do these files get stored on the device?
On Android, AsyncStorage will use either SQLite or RocksDB based on availability. You can find the databases in the following locations:
/data/data/<Your-Application-Package-Name>/databases/<your-data-base-name>
Pre-requisite:
Steps:
1. Install the vulnerable app on the emulator/physical device and ensure data is stored in AsyncStorage.
2. Run the following command to run the ADB daemon as root:
adb root
3. Access the shell of the device via adb shell
4. Navigate to the databases folder of the application
cd /data/data/<com.your.package>/databases</com.your.package>
5. You will find 3 files in this folder. You can check the contents of each file with
6. You will be able to see data stored in AsyncStorage in cleartext.
In Android applications, XML files play important roles in defining layouts of components, storing recurring strings, providing IDs to the assets, etc. Developers sometimes store sensitive information in plaintext in these XML files. We can review these XML files to find the application’s hardcoded secrets.
While looking for sensitive information in XML files, the “Strings.xml” should be the first place to look. For convenience, developers might include frequently needed sensitive information such as credentials, static tokens, passwords, secrets, and hidden URLs in the Strings.xml file, which can later be referenced within the application. This file can be located in the”/res/values/” folder of the decompiled application.
The “AndroidManifest.xml” file contains information on the application package, including application components such as activities, broadcast receivers, services, content providers, etc. This file may contain sensitive hardcoded strings such as keys, secrets, tokens, etc.
Android applications can keep all kinds of stuff in the package cache folder, which helps boost the app’s performance. However, sometimes, the application may save sensitive information in the cache folder due to misconfiguration, more specifically, in the “HTTP” folder.
In the React Native applications, “http-cache” contains the GET-based HTTP request+response data. This may expose sensitive data if it is being transferred over an unencrypted or insecure channel.
1. Only “GET” based HTTP request+response data is stored in the cache folder of the application. Post-request data is not cached in the cache folder.
2. Only unencrypted (non-https) requests are cached in plaintext. If the URL has an SSL certificate implemented, then the data will be cached in an encrypted format.
1. Any GET HTTP request is saved in this folder. If the application sends sensitive data, such as OAuth tokens, credentials, etc., over the GET request type, then we can grab that data in plaintext.
2. Both request and response headers and their values are cached in plaintext. Thus, we can grab that data if the application sends any sensitive information in the request/response headers of a GET request.
1. Open the vulnerable application with the feature to transfer data in HTTP requests and issue some HTTP requests.
2. Open a command prompt and start the ADB server as root with the following command: adb root
3. Now access the shell of the device with adb shell and navigate to the following directory
/data/data/<com.package.name>/cache/http-cache</com.package.name>
4. Open all files in this folder with cat *, and you can now scrap through cached data
Maintaining the security of application code is essential for protecting sensitive information. React Native apps, with their bundled JavaScript code, present unique challenges, but these can be mitigated through careful development practices. By proactively securing sensitive data, developers can significantly reduce the exposure risk and ensure their applications’ integrity.
The upcoming blog of the React Native Pentesting for Android Security Masterclass has some really exciting stuff in store. In it, we will learn how to edit and patch React Native applications.
References: