Post

React Native Application Pentest

React Native Application Pentest

Introduction

React Native, introduced by Facebook in 2015, is a JavaScript-based framework for developing native applications on platforms like Android and iOS. Its cross-platform capabilities have made it increasingly popular in mobile application development. This blog delves into the nuances of pentesting React Native applications.


React Native Application Architecture

React Native applications are crafted using JavaScript and JSX. A central concept is the “component,” which represents segments of the user interface, akin to “activities” in Java-based Android apps. These components are both composable and reusable, facilitating efficient development.

React Native Architecture


Reverse Engineering React Native Applications

To analyze a React Native application’s structure:

  1. Decompile the APK: Use tools like APKTool to extract the application’s contents.
  2. Identify Key Files: Look for the index.android.bundle file in the /assets directory. This file houses the application’s JavaScript code in a minified format.

The Mystery of index.android.bundle File

The index.android.bundle is pivotal as it contains the entire core logic of the application. Pentesters can search this file for hardcoded credentials, tokens, and other sensitive information.


Hermes Engine

Hermes is an open-source JavaScript engine optimized for React Native. It enhances performance by compiling JavaScript into bytecode ahead of time. However, this introduces challenges in reverse engineering due to the bytecode format.


Presenting hbctool

To address the challenges posed by Hermes bytecode, the blog introduces hbctool. This tool aids in decompiling Hermes bytecode, allowing pentesters to analyze the underlying JavaScript code effectively.


Now Let’s Dive into Solving the Challenge to Explore React Native Architecture

After downloading our APK and opening it using JADX for static analysis, we found the following path: /assets under the Resources folder:

We found all the logic code written in JavaScript, and it was minified to save memory and add some obfuscation. We can unminify this file using the online tool: unminify.

Copying the whole code to this website to unminify it:

Now, we can download the unminified code to our VSCode to investigate it further.

NOTE: We are lucky because this code doesn’t use the Hermes engine, making it easier to analyze. If it used Hermes, you could decode it using hbctool as mentioned above.

After opening the code in VSCode and doing some searching, I found this piece of code:

1
2
3
function y(t){
  t == f("flag{N0t_Th4t_E4sy}") ("616b66607c37333662363f3036613f365643f3f663366306161643335613632653e653f667a") ? j("Correct Flag") : j("Wrong Flag");
}

Of course, I tried to submit this flag, but it wasn’t the real flag, so I investigated more about this function and followed its parameters:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
f = function (t) {
    var n = function (n) {
        return ((o = t),
        o.split("").map(function (t) {
            return t.charCodeAt(0);
        })).reduce(function (t, n) {
            return t ^ n;
        }, n);
        var o;
    };
    return function (t) {
        return t
            .match(/.{1,2}/g)
            .map(function (t) {
                return parseInt(t, 16);
            })
            .map(n)
            .map(function (t) {
                return String.fromCharCode(t);
            })
            .join("");
    };
};

Analysis of the Code

Inner Function n:

  • Takes a single integer n (which is a part of the decrypted hex string).
  • Converts the key (t) to ASCII codes using t.charCodeAt(0).
  • XORs all the ASCII values of the key characters with n.
  • Returns the XORed value.

Outer Function:

  • Splits the hex string into chunks of two characters each (.{1,2}).
  • Converts each hex chunk to an integer using parseInt(t, 16).
  • Passes each integer to the inner function n.
  • Converts the result to a character using String.fromCharCode().
  • Joins all characters to produce the decoded string.

After analyzing the code, let’s create a simple script to replicate this decryption process:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
key = "flag{N0t_Th4t_E4sy}"
hex_string = "616b66607c3733366236f3036613f3765643f3f66336630616143335613632653e653f667a"

def decode_string(key, hex_string):
    key_codes = [ord(ch) for ch in key]
    hex_codes = [int(hex_string[i:i+2], 16) for i in range(0, len(hex_string), 2)]

    decoded_chars = []
    for hex_code in hex_codes:
        decoded_char = hex_code
        for key_code in key_codes:
            decoded_char ^= key_code
        decoded_chars.append(chr(decoded_char))

    return ''.join(decoded_chars)

decoded_flag = decode_string(key, hex_string)
print("Decoded Flag:", decoded_flag)

Running the script successfully reveals the correct flag:


For a detailed exploration, refer to the full blog post: Payatu Blog

THANKS FOR READING ❤️

This post is licensed under CC BY 4.0 by the author.