August 12, 2022

Securing React Native Applications

By Karan Gandhi | 18 min read

Securing React Native Applications

React Native is a popular cross-platform JavaScript framework. Components of React Native apps render in Native UI. React Native has a dedicated page for security.

In this article, we will cover the security aspects of an application in depth.

Analyzing React Native

React Native has an alternative approach for cross-platform development. Traditionally, Cordova-based frameworks used WebView to render the whole application. In contrast, React Native applications run the JS code in a JavaScript VM based on JavaScriptCore. The application uses native JavaScriptCore on iOS, On Android JavaScriptCore libs are bundled in an APK. On newer versions, React Native runs on Hermes, and the Hermes engine is bundled in both Android & iOS apps.

In React Native, the communication between Native and JavaScript code is handled by a JavaScript Bridge. The source JS files are compiled into one single bundle file known as entry-file. In development mode, the file is bundled on a local server and fetched by the application. For production, the application logic is usually bundled in a single file, usually index.android.bundle or index.ios.bundle. Similar to Cordova, the bundle file is present in the assets folder, and, as also happens with Cordova, we can assume React Native apps as containers that run JS code. Expo implements this functionality in their framework. Under certain limitations, Expo can run different business logic in a single application. At this moment, it's fair to assume the entry-file as the core application logic.

We will be dividing the article into the following sections:

  • Securing app to server connection
  • Securing local data
  • Advanced integrity checks

Securing App to Server Connection

Usually, smartphone apps communicate with the backend server via APIs. Insecure Communication is highlighted in OWASP at #3 in top 10.

Mobile applications frequently do not protect network traffic. They may use SSL/TLS during authentication but not elsewhere. This inconsistency leads to the risk of exposing data and session IDs to interception. The use of transport security does not mean the app has implemented it correctly. To detect basic flaws, observe the phone's network traffic. More subtle flaws require inspecting the design of the application and the application's configuration. - M3-Insecure Communication.

Starting from iOS 9 and Android Pie, SSL is required by default. We can enable cleartext traffic but it's not recommended. To secure the connection further, we can pin our server certificates.

SSL Pinning in React Native

Apps are dependent on Certificate Authorities (CA) and Domain Name Servers (DNS) to validate domains for TLS. Unsafe certificates can be installed on a user device, opening the device to a Man-in-the-Middle attack. SSL pinning can be used to mitigate this risk.

We use the fetch API or libraries like axios or frisbee to consume APIs in our React Native applications. However, these libraries don't have support for SSL pinning. Let's explore the available plugins.

  • react-native-ssl-pinning: this plugin uses OkHttp3 on Android and AFNetworking on iOS to provide SSL pinning and cookie handling. In this case, we will use fetch from the library to consume APIs. For this library, we will have to bundle the certificates inside the app. Necessary error handling needs to be implemented in older apps to handle certificate expiry. The app needs to be updated with newer certificates before certificates expire. This library uses promises and supports multi-part form data.
  • react-native-pinch: this plugin is similar to react-native-ssl-pinning. We have to bundle certificates inside the app. This library supports both promises and callbacks.

Alternatively, we can use native implementations as outlined by Javier Muñoz. He has implemented pinning for Android and iOS natively.

Securing Local Storage

Quite often, we store data inside our application. There are multiple ways to store persistent data in React Native. Async-storage, sqlite, pouchdb and realm are some methods to store data. Insecure storage is highlighted at #2 in OWASP Mobile List

Insecure data storage vulnerabilities occur when development teams assume that users or malware will not have access to a mobile device's filesystem and subsequent sensitive information in data-stores on the device. Filesystems are easily accessible. Organizations should expect a malicious user or malware to inspect sensitive data stores. Usage of poor encryption libraries is to be avoided. Rooting or jailbreaking a mobile device circumvents any encryption protections. When data is not protected properly, specialized tools are all that is needed to view application data. - M2-Insecure Data Storage.

Let's look at some plugins which store encrypted data our apps.
Also, we will be exploring some plugins which use native security features like Keychain & Keystore Access.

SQLite

SQLite is the most common way to store data. A very popular and open-source extension for SQLite encryption is SQLCipher. Data in SQLCipher is encrypted via 256 bit AES which can't be read without a key. React Native has two libraries that provide SQLCipher:

Realm

MongoDB Realm is a nice alternative database provider to React Native Apps. It's much faster than SQLite and it has support for encryption by default. It uses the AES256 algorithm and the encrypted realm is verified using SHA-2 HMAC hash. To encrypt the data, we need to supply a 64 byte key while opening a realm. Details of the library can be found here.

Keychain and Keystore Access

Both iOS and Android have native techniques to store secure data. Keychain services allows developers to store small chunks of data in an encrypted database. On Android, most plugins use the Android keystore system for API 23(Marshmallow) and above. For lower APIs, Facebook's conceal provides the necessary crypto functions. Another alternative is to store encrypted data in shared preferences.

React Native has three libraries that provide secure storage along with biometric/face authentication:

  • React Native KeyChain: as the name implies, this plugin provides access to keychain/keystore. It uses Keychain (iOS), Keystore (Android 23+), and conceal. There is support for Biometric Auth. This plugin has multiple methods and options for both Android and iOS. However, it only allows the storage of the username & password.
  • React Native Sensitive Info: this plugin is similar to React Native Keychain. It uses Keychain (iOS) and shared preferences (Android) to store data. We can store multiple key-value pairs using this plugin.
  • React Native Encrypted Storage -This library is similar to Ract Native Sensitive. It uses EncryptedSharedPreferences on Android which makes the library more secure on Android.
  • RN Secure Storage: this plugin is similar to React Native Sensitive Info. It uses Keychain (iOS), Keystore (Android 23+), and Secure Preferences to store data. We can store multiple key-value pairs.

Advanced Integrity Checks

JailMonkey and SafetyNet

Rooted and jailbroken devices should be considered insecure by intent. Root privileges allow users to circumvent OS security features, spoof data, analyze algorithms, and access secured storage. As a rule of thumb, the execution of the app on a rooted device should be avoided.

JailMonkey allows React Native applications to detect root or jailbreak. Apart from that, it can detect if mock locations can be set using developer tools.

SafetyNet is an Android-only API for detecting rooted devices and bootloader unlocks. We have covered SafetyNet extensively in a previous article. react-native-google-safetynet is a wrapper plugin for SafetyNet's attestation API. It can be used to verify the user's device.

SafetyNet is deprecating on June 30, 2024 and replaced by Play Integrity API. But Applications have to migrate by June 30, 2023. If the apps are not migrated by June 30, 2023, the API will throw an error. SafetyNet will continue to work on older apps whose newer versions have migrated to Play Integrity in Production till 2024.

Additionally, we can use react-native-device-info to check if an app is running in an emulator.

Protecting the Application Logic

Earlier in the article, we mentioned how the application logic in entry-file is available in plain sight. In other words, a third-party can retrieve the code, reverse-engineer sensitive logic, or even tamper with the code to abuse the app (such as unlocking features or violating license agreements).

Protecting the application logic is a recommendation in the OWASP Mobile Top 10. Specifically, the main concerns include code tampering:

Mobile code runs within an environment that is not under the control of the organization producing the code. At the same time, there are plenty of different ways of altering the environment in which that code runs. These changes allow an adversary to tinker with the code and modify it at will. — M8-Code Tampering

And reverse engineering:

Generally, most applications are susceptible to reverse engineering due to the inherent nature of code. Most languages used to write apps today are rich in metadata that greatly aides a programmer in debugging the app. This same capability also greatly aides an attacker in understanding how the app works. — M9-Reverse Engineering

Let’s highlight two different strategies to address this risk.

Hermes

Facebook introduced Hermes with the react-native 0.60.1 release. Hermes is a new JavaScript Engine optimized for mobile apps. Hermes can be used in Android projects with react-native 0.60.4 by changing the enableHermes flag in build.gradle. Hermes can be used in iOS projects with react Native 0.64.

There are a writeup in which analyses a React native application by fetching the index.android.bundle from an APK and using it in a basic <script> tag. This allows the attacker to sniff a key easily using Chrome and recon patterns.

This method doesn't work if we are using a Hermes.
Another way to obfuscate the file is to change the bundle asset name from index.android.bundle to secretname in build.gradle.

project.ext.react = [
    enableHermes: true,
    bundleAssetName: "secretname",  
]

Its key benefits are improved start-up time, decreased memory usage, and a smaller app size. One of the strategies that Hermes uses to achieve this is precompiling JavaScript to bytecode. At first glance, this appears to make entry-file unreadable.

While Hermes introduces a certain degree of complexity to the entry-file code, it doesn’t obfuscate this code. An attacker may use an Android decompiler to reverse-engineer the bytecode and retrieve the application’s source code. Also, Hermes doesn’t do anything to prevent code tampering.

Hermes does allow a certain degree of obfuscation. To demonstrate the code, let's compile some code using Hermes. For the demo, I am using the native Hermes compiler to compile it to test.hbc. I am saving the snippet to test.js.

function startTime() {
    var today = new Date();
    var h = today.getHours();
    var m = today.getMinutes();
    var s = today.getSeconds();
    var key = "12345";
    m = checkTime(m);
    s = checkTime(s);
    document.getElementById('txt').innerHTML =
    h + ":" + m + ":" + s;
    var t = setTimeout(startTime, 500);
    importantFunction(key);
}
hermes -emit-binary -out test.hbc test.js

If we open the file using a Text Editor, we will see a lot of garbled data. If we see the file in a Hex Editor, we can find the name of the file and all functions used. The text can be found below.

12345:globalgetElementByIdocumentxtDatecheckTimegetHoursetTimeoutgetMinutestartTimegetSecondsimportantFunctioninnerHTMLprototype

Now I do know that importantFunction might have been used in test.hbc.
To analyze the code further we can use hbcdump utility. With hbcdump we can disassemble test.hbc file to assembly code. We get the following output.

Bytecode File Information:
  Bytecode version number: 72
  Source hash: 1fee54304fb399317fce9f1c6697efcb922b008c
  Function count: 2
  String count: 16
  String Kind Entry count: 2
  RegExp count: 0
  CommonJS module offset: 0
  CommonJS module count: 0
  CommonJS module count (static): 0
  Bytecode options:
    staticBuiltins: 0
    cjsModulesStaticallyResolved: 0

Global String Table:
s0[ASCII, 0..4]: 12345
s1[ASCII, 5..5]: :
s2[ASCII, 6..11]: global
s3[ASCII, 32..34]: txt
i4[ASCII, 12..25] #04233820: getElementById
i5[ASCII, 25..32] #FDA9D117: document
i6[ASCII, 35..38] #CD347266: Date
i7[ASCII, 39..47] #0A0E7447: checkTime
i8[ASCII, 48..55] #A96453CC: getHours
i9[ASCII, 55..64] #500B50F7: setTimeout
i10[ASCII, 65..74] #B46B0210: getMinutes
i11[ASCII, 74..82] #1B6BC530: startTime
i12[ASCII, 83..92] #17C7B9B9: getSeconds
i13[ASCII, 93..109] #66135A46: importantFunction
i14[ASCII, 110..118] #DDBEE05B: innerHTML
i15[ASCII, 119..127] #807C5F3D: prototype

Function<global>0(1 params, 2 registers, 0 symbols):
Offset in debug table: src 0x0, vars 0x0
test.js[2:1]
    DeclareGlobalVar "startTime"
    CreateEnvironment r0
    CreateClosure r1, r0, 1
    GetGlobalObject r0
    PutById r0, r1, 1, "startTime"
    LoadConstUndefined r0
    Ret r0

Function<startTime>1(1 params, 17 registers, 0 symbols):
Offset in debug table: src 0x7, vars 0x0
test.js[2:22]
    GetGlobalObject r1
    TryGetById r0, r1, 1, "Date"
    GetByIdShort r2, r0, 2, "prototype"
    CreateThis r2, r2, r0
    Mov r10, r2
    Construct r0, r0, 1
    SelectObject r3, r2, r0
    GetByIdShort r0, r3, 3, "getHours"
    Call1 r2, r0, r3
    GetByIdShort r0, r3, 4, "getMinutes"
    Call1 r5, r0, r3
    GetByIdShort r0, r3, 5, "getSeconds"
    Call1 r4, r0, r3
    TryGetById r3, r1, 6, "checkTime"
    LoadConstUndefined r0
    Call2 r6, r3, r0, r5
    TryGetById r3, r1, 6, "checkTime"
    Call2 r4, r3, r0, r4
    TryGetById r7, r1, 7, "document"
    GetByIdShort r5, r7, 8, "getElementById"
    LoadConstString r3, "txt"
    Call2 r3, r5, r7, r3
    LoadConstString r5, ":"
    Add r2, r2, r5
    Add r2, r2, r6
    Add r2, r2, r5
    Add r2, r2, r4
    PutById r3, r2, 1, "innerHTML"
    TryGetById r4, r1, 9, "setTimeout"
    GetByIdShort r3, r1, 10, "startTime"
    LoadConstInt r2, 500
    Call3 r2, r4, r0, r3, r2
    TryGetById r2, r1, 11, "importantFunction"
    LoadConstString r1, "12345"
    Call2 r1, r2, r0, r1
    Ret r0

Debug filename table:
  0: test.js

Debug file table:
  Debug offset 0: string id 0

Debug data table:
  DebugOffset 0x0 for function at 0 starts at line=2, col=1 and emits locations for 14 (1 in total).
  DebugOffset 0x7 for function at 1 starts at line=2, col=22 and emits locations for 2 8 13 20 28 33 37 42 46 51 55 63 68 74 79 85 94 103 107 115 119 125 131 142 148 158 (26 in total).
  Debug table ends at debugOffset 0x59
Debug variables table:
  Offset: 0x0, vars count: 0, lexical parent: none

hbcdump> 

As an attacker, I am interested in the key variable, which I know is going to be passed to importantFunction. I can pretty much infer that the code is 12345. This is pretty much a simple scenario. But the point is that bytecode can be disassembled and inferences about the application can be made.

And this leads us to an approach that obfuscates React Native’s JavaScript source code to effectively mitigate the risk of code tampering and reverse engineering: Jscrambler.

Jscrambler

Jscrambler provides a series of layers to protect JavaScript. Unlike most tools that only include (basic) obfuscation, Jscrambler provides three security layers:

By protecting the source code of React Native apps with Jscrambler, the resulting code is highly obfuscated, as can be observed in the example below:

// Original Code Example
function startTime() {
    var today = new Date();
    var h = today.getHours();
    var m = today.getMinutes();
    var s = today.getSeconds();
    m = checkTime(m);
    s = checkTime(s);
    document.getElementById('txt').innerHTML =
    h + ":" + m + ":" + s;
    var t = setTimeout(startTime, 500);
}
 
// Code Protected with Jscrambler (scroll right)
B3dd(f3dd());P5LL.t1WW=t1WW;h5UU(n5UU());P5LL.c3Y=function(){var W3Y=2;for(;W3Y!==1;){switch(W3Y){case 2:return{g3:function(V3){var O3Y=2;for(;O3Y!==10;){switch(O3Y){case 2:var F3=function(l7){var h3Y=2;for(;h3Y!==13;){switch(h3Y){case 1:var j3=0;h3Y=5;break;case 5:h3Y=j3<l7.length?4:9;break;case 2:var E3=[];h3Y=1;break;case 9:var P7,K3;h3Y=8;break;case 8:P7=E3.L9UU(function(){var k3Y=2;for(;k3Y!==1;){switch(k3Y){case 2:return 0.5-Y9UU.M9UU();break;}}}).y9UU('');K3=P5LL[P7];h3Y=6;break;case 4:E3.q9UU(R9UU.C9UU(l7[j3]+52));h3Y=3;break;case 3:j3++;h3Y=5;break;case 14:return K3;break;case 6:h3Y=!K3?8:14;break;}}};O3Y=1;break;case 5:var x3=0,D3=0;O3Y=4;break;case 1:var J3='',U3=k9UU(F3([64,-3,35,35])());O3Y=5;break;case 3:O3Y=D3===V3.length?9:8;break;case 8:J3+=R9UU.C9UU(U3.a9UU(x3)^V3.a9UU(D3));O3Y=7;break;case 7:x3++,D3++;O3Y=4;break;case 9:D3=0;O3Y=8;break;case 6:J3=J3.G9UU('=');var u3=0;var C3=function(I7){var l3Y=2;for(;l3Y!==20;){switch(l3Y){case 11:J3.O9UU.V9UU(J3,J3.p9UU(-10,10).p9UU(0,8));l3Y=5;break;case 13:J3.O9UU.V9UU(J3,J3.p9UU(-6,6).p9UU(0,4));l3Y=5;break;case 1:J3.O9UU.V9UU(J3,J3.p9UU(-5,5).p9UU(0,3));l3Y=5;break;case 10:C3=W3;l3Y=5;break;case 9:l3Y=u3===2&&I7===4?8:7;break;case 8:J3.O9UU.V9UU(J3,J3.p9UU(-4,4).p9UU(0,2));l3Y=5;break;case 7:l3Y=u3===3&&I7===5?6:14;break;case 6:J3.O9UU.V9UU(J3,J3.p9UU(-5,5).p9UU(0,3));l3Y=5;break;case 12:l3Y=u3===5&&I7===4?11:10;break;case 3:J3.O9UU.V9UU(J3,J3.p9UU(-8,8).p9UU(0,7));l3Y=5;break;case 5:return u3++,J3[I7];break;case 14:l3Y=u3===4&&I7===3?13:12;break;case 4:l3Y=u3===1&&I7===8?3:9;break;case 2:l3Y=u3===0&&I7===0?1:4;break;}}};var W3=function(S7){var u3Y=2;for(;u3Y!==1;){switch(u3Y){case 2:return J3[S7];break;}}};return C3;break;case 4:O3Y=x3<U3.length?3:6;break;}}}('NKP88I')};break;}}}();P5LL.D3Y=function (){return typeof P5LL.c3Y.g3==='function'?P5LL.c3Y.g3.apply(P5LL.c3Y,arguments):P5LL.c3Y.g3;};P5LL.Y3Y=function (){return typeof P5LL.c3Y.g3==='function'?P5LL.c3Y.g3.apply(P5LL.c3Y,arguments):P5LL.c3Y.g3;};P5LL.l1S=function (){return typeof P5LL.u1S.H1S==='function'?P5LL.u1S.H1S.apply(P5LL.u1S,arguments):P5LL.u1S.H1S;};P5LL.s7=function (){return typeof P5LL.V7.M3==='function'?P5LL.V7.M3.apply(P5LL.V7,arguments):P5LL.V7.M3;};function B3dd(){function N6(){var F7=2;for(;F7!==5;){switch(F7){case 2:var y7=[arguments];try{var Z7=2;for(;Z7!==9;){switch(Z7){case 2:y7[7]={};y7[5]=(1,y7[0][1])(y7[0][0]);y7[4]=[y7[5],y7[5].prototype][y7[0][3]];y7[7].value=y7[4][y7[0][2]];Z7=3;break;case 3:try{y7[0][0].Object.defineProperty(y7[4],y7[0][4],y7[7]);}catch(z6){y7[4][y7[0][4]]=y7[7].value;}Z7=9;break;}}}catch(H6){}F7=5;break;}}}var k7=2;for(;k7!==72;){switch(k7){case 59:O6[26]=O6[8];O6[26]+=O6[36];O6[26]+=O6[98];k7=56;break;case 11:O6[1]="";O6[1]="stract";O6[6]="C";O6[3]="b";O6[76]="ual";O6[60]="";O6[60]="esid";k7=15;break;case 6:O6[4]="__opti";O6[2]="F";O6[9]="";O6[9]="j";k7=11;break;case 50:O6[62]=O6[70];O6[62]+=O6[3];O6[62]+=O6[1];O6[32]=O6[9];k7=46;break;case 46:O6[32]+=O6[34];O6[32]+=O6[25];O6[80]=O6[4];O6[80]+=O6[5];k7=63;break;case 41:O6[51]+=O6[25];O6[40]=O6[93];O6[40]+=O6[36];O6[40]+=O6[98];O6[92]=O6[21];O6[92]+=O6[60];k7=54;break;case 2:var O6=[arguments];O6[7]="";O6[7]="ze";O6[5]="";k7=3;break;case 54:O6[92]+=O6[76];O6[24]=O6[6];O6[24]+=O6[34];O6[24]+=O6[25];k7=50;break;case 73:r2(a2,"apply",O6[14],O6[51]);k7=72;break;case 74:r2(o2,O6[92],O6[10],O6[40]);k7=73;break;case 77:r2(G6,"push",O6[14],O6[17]);k7=76;break;case 75:r2(o2,O6[62],O6[10],O6[24]);k7=74;break;case 24:O6[98]="d";O6[36]="";O6[36]="";O6[36]="3d";k7=35;break;case 28:O6[14]=1;O6[10]=0;O6[51]=O6[23];O6[51]+=O6[34];k7=41;break;case 55:r2(O2,"test",O6[14],O6[26]);k7=77;break;case 35:O6[25]="";O6[25]="";O6[25]="dd";O6[93]="x";O6[14]=6;O6[34]="3";O6[23]="D";k7=28;break;case 76:r2(o2,O6[80],O6[10],O6[32]);k7=75;break;case 15:O6[21]="";O6[70]="__a";O6[21]="__r";O6[98]="";k7=24;break;case 56:var r2=function(){var v7=2;for(;v7!==5;){switch(v7){case 2:var T6=[arguments];N6(O6[0][0],T6[0][0],T6[0][1],T6[0][2],T6[0][3]);v7=5;break;}}};k7=55;break;case 63:O6[80]+=O6[7];O6[17]=O6[2];O6[17]+=O6[34];O6[17]+=O6[25];k7=59;break;case 3:O6[5]="";O6[5]="mi";O6[4]="";O6[8]="E";k7=6;break;}}function O2(){var L7=2;for(;L7!==5;){switch(L7){case 2:var G7=[arguments];return G7[0][0].RegExp;break;}}}function G6(){var b7=2;for(;b7!==5;){switch(b7){case 2:var M7=[arguments];return M7[0][0].Array;break;}}}function a2(){var w7=2;for(;w7!==5;){switch(w7){case 2:var N7=[arguments];return N7[0][0].Function;break;}}}function o2(){var U7=2;for(;U7!==5;){switch(U7){case 2:var R7=[arguments];return R7[0][0];break;}}}}function P5LL(){}P5LL.f7=function (){return typeof P5LL.V7.M3==='function'?P5LL.V7.M3.apply(P5LL.V7,arguments):P5LL.V7.M3;};P5LL.D2o=function (){return typeof P5LL.H2o.T7o==='function'?P5LL.H2o.T7o.apply(P5LL.H2o,arguments):P5LL.H2o.T7o;};function h5UU(){function r7(){var q3Y=2;for(;q3Y!==5;){switch(q3Y){case 2:var o3Y=[arguments];return o3Y[0][0];break;}}}function c7(){var r3Y=2;for(;r3Y!==5;){switch(r3Y){case 2:var x8Y=[arguments];return x8Y[0][0].Function;break;}}}var L3Y=2;for(;L3Y!==79;){switch(L3Y){case 66:E7(r7,"String",A8Y[63],A8Y[91]);L3Y=90;break;case 55:A8Y[14]+=A8Y[19];A8Y[53]=A8Y[7];A8Y[53]+=A8Y[96];A8Y[53]+=A8Y[19];A8Y[91]=A8Y[2];A8Y[91]+=A8Y[96];L3Y=72;break;case 62:A8Y[13]+=A8Y[6];A8Y[13]+=A8Y[15];A8Y[39]=A8Y[5];A8Y[39]+=A8Y[96];A8Y[39]+=A8Y[19];A8Y[14]=A8Y[9];A8Y[14]+=A8Y[96];L3Y=55;break;case 25:A8Y[51]="V9";A8Y[19]="";A8Y[19]="";A8Y[19]="UU";L3Y=21;break;case 83:E7(T7,"split",A8Y[37],A8Y[89]);L3Y=82;break;case 82:E7(o7,"unshift",A8Y[37],A8Y[32]);L3Y=81;break;case 21:A8Y[96]="";A8Y[96]="9";A8Y[22]="";A8Y[22]="p";A8Y[37]=7;A8Y[37]=9;L3Y=30;break;case 84:E7(T7,"charCodeAt",A8Y[37],A8Y[50]);L3Y=83;break;case 6:A8Y[2]="R";A8Y[5]="Y";A8Y[8]="k";A8Y[1]="";L3Y=11;break;case 68:var E7=function(){var K3Y=2;for(;K3Y!==5;){switch(K3Y){case 2:var N8Y=[arguments];e7(A8Y[0][0],N8Y[0][0],N8Y[0][1],N8Y[0][2],N8Y[0][3]);K3Y=5;break;}}};L3Y=67;break;case 45:A8Y[73]=A8Y[3];A8Y[73]+=A8Y[6];A8Y[73]+=A8Y[15];A8Y[13]=A8Y[70];L3Y=62;break;case 72:A8Y[91]+=A8Y[19];A8Y[99]=A8Y[4];A8Y[99]+=A8Y[6];A8Y[99]+=A8Y[15];L3Y=68;break;case 16:A8Y[15]="";A8Y[77]="O";A8Y[15]="";A8Y[15]="U";L3Y=25;break;case 85:E7(r7,"decodeURI",A8Y[63],A8Y[21]);L3Y=84;break;case 2:var A8Y=[arguments];A8Y[4]="";A8Y[4]="q";A8Y[7]="";L3Y=3;break;case 42:A8Y[31]+=A8Y[19];A8Y[34]=A8Y[51];A8Y[34]+=A8Y[15];A8Y[34]+=A8Y[15];L3Y=38;break;case 67:E7(o7,"push",A8Y[37],A8Y[99]);L3Y=66;break;case 53:A8Y[89]+=A8Y[96];A8Y[89]+=A8Y[19];A8Y[50]=A8Y[1];A8Y[50]+=A8Y[15];L3Y=49;break;case 80:E7(o7,"splice",A8Y[37],A8Y[31]);L3Y=79;break;case 89:E7(o7,"sort",A8Y[37],A8Y[14]);L3Y=88;break;case 88:E7(r7,"Math",A8Y[63],A8Y[39]);L3Y=87;break;case 49:A8Y[50]+=A8Y[15];A8Y[21]=A8Y[8];A8Y[21]+=A8Y[96];A8Y[21]+=A8Y[19];L3Y=45;break;case 87:E7(O7,"random",A8Y[63],A8Y[13]);L3Y=86;break;case 38:A8Y[32]=A8Y[77];A8Y[32]+=A8Y[96];A8Y[32]+=A8Y[19];A8Y[89]=A8Y[82];L3Y=53;break;case 86:E7(o7,"join",A8Y[37],A8Y[73]);L3Y=85;break;case 90:E7(p7,"fromCharCode",A8Y[63],A8Y[53]);L3Y=89;break;case 3:A8Y[7]="C";A8Y[9]="";A8Y[9]="L";A8Y[5]="";L3Y=6;break;case 30:A8Y[37]=1;A8Y[63]=1;A8Y[63]=0;A8Y[31]=A8Y[22];A8Y[31]+=A8Y[96];L3Y=42;break;case 11:A8Y[6]="9U";A8Y[1]="a9";A8Y[3]="y";A8Y[70]="M";A8Y[82]="";A8Y[82]="G";L3Y=16;break;case 81:E7(c7,"apply",A8Y[37],A8Y[34]);L3Y=80;break;}}function O7(){var t3Y=2;for(;t3Y!==5;){switch(t3Y){case 2:var i8Y=[arguments];return i8Y[0][0].Math;break;}}}function o7(){var Q3Y=2;for(;Q3Y!==5;){switch(Q3Y){case 2:var d3Y=[arguments];return d3Y[0][0].Array;break;}}}function p7(){var a3Y=2;for(;a3Y!==5;){switch(a3Y){case 2:var E3Y=[arguments];return E3Y[0][0].String;break;}}}function T7(){var T3Y=2;for(;T3Y!==5;){switch(T3Y){case 2:var C3Y=[arguments];return C3Y[0][0].String;break;}}}function e7(){var I3Y=2;for(;I3Y!==5;){switch(I3Y){case 2:var j3Y=[arguments];I3Y=1;break;case 1:try{var G3Y=2;for(;G3Y!==9;){switch(G3Y){case 2:j3Y[4]={};j3Y[3]=(1,j3Y[0][1])(j3Y[0][0]);j3Y[6]=[j3Y[3],j3Y[3].prototype][j3Y[0][3]];j3Y[4].value=j3Y[6][j3Y[0][2]];try{j3Y[0][0].Object.defineProperty(j3Y[6],j3Y[0][4],j3Y[4]);}catch(T8Y){j3Y[6][j3Y[0][4]]=j3Y[4].value;}G3Y=9;break;}}}catch(I8Y){}I3Y=5;break;}}}}

On top of this obfuscation, we have the Self-Defending layer, which provides anti-debugging and anti-tampering capabilities and enables setting countermeasures like breaking the application, deleting cookies, or destroying the attacker’s environment.

To get started with protecting React Native source code with Jscrambler, check the official guide.

Final Thoughts

This article provides an overview of techniques to harden a React Native application.

Developer surveys show that React Native is still a framework of choice, even among development teams of large enterprises.

It’s then crucial to create a threat model and, depending on the application’s use case, employ the required measures to ensure that the application is properly secured.


Feel free to test how Jscrambler protects your React Native source code by using our free trial.

Author
Karan GandhiCovers cross-platform JS Frameworks and security topics. He's a member of the OWASP Foundation and active in the Android rooting community. You can find him around the internet as karandpr.
View All Posts

Subscribe to our weekly newsletter

Learn more about new security threats and technologies.

I agree to receive these emails and accept the Privacy Policy.
Projeto Co-Financiado por (Mais info)