React Native Application Static Analysis [English]
สำหรับเวอร์ชันภาษาไทย อ่านได้ที่ https://suam.wtf/posts/react-native-application-static-analysis-th/
In the previous year (2020), I performed penetration testing on many mobile applications, and many of them are implemented with cross-platform frameworks e.g., Xamarin, React Native, or Flutter. I think in the upcoming years, an increasing amount of mobile applications will be developed with these frameworks.
For each framework, different reverse engineering and static analysis methods are used. Moreover, for the newer frameworks, reverse engineering materials and tools are lacking. As a result, it is very hard to perform static analysis and patch these applications if you don’t understand the internals of these frameworks.
React Native application was one of the most common types found, and they had been the easiest to perform reverse engineering. However, Facebook has implemented their own JavaScript Engine named Hermes. Some React Native applications have already migrated to the Hermes engine. Additionally, the Hermes engine has also been bundled in many React Native starter kits, so enabling it is very easy.
Without the Hermes engine, the React Native JavaScript source code is just minified before being stored in the application. Thus, to do reverse engineering, only beautifying is needed before reading or patching it. But with the Hermes engine, JavaScript source code is compiled to the Hermes bytecode before being stored in the application.
Hermes is a new custom JavaScript engine. Therefore, the only way to understand and know how to patch it is by reading the Hermes engine source code from Github. This is not very different from the AOT snapshot of Flutter, or .NET DLL bundled in the native lib of Xamarin. When I found them in pentest projects, they always made me frustrated.
React Native without Hermes engine
If you used to perform reverse engineering on the mobile application, you must know that it is very easy. It is hardly different from web hacking. In the following examples, the Android application will be used because it is easier to create and set up labs. Besides, for React Native, Android and iOS are hardly different.
You can follow this tutorial using this application (ReactNativeReverseingLab.apk).
After installing and launching the application, tap on the + button twice. The Increase button has already been broken.
message will be shown and you will not be able to increase the counter
anymore.
The goal of this lab is to increase the counter to 10. Therefore, application patching. It is very simple as follows:
(1) Unzip the APK file (you can also use apktool
in this step).
(hack) bongtrop@bongtrop-pc:lab/ $ unzip ReactNativeReversingLab.apk -d ReactNativeReversingLab
Archive: ReactNativeReverseingLab.apk
inflating: ReactNativeReverseingLab/AndroidManifest.xml
inflating: ReactNativeReverseingLab/META-INF/CERT.RSA
inflating: ReactNativeReverseingLab/META-INF/CERT.SF
inflating: ReactNativeReverseingLab/META-INF/MANIFEST.MF
[...]
(2) Navigate to ReactNativeReversingLab/asserts
. The index.android.bundle
will be found.
(hack) bongtrop@bongtrop-pc:lab/ $ cd ReactNativeReverseingLab/assets
(hack) bongtrop@bongtrop-pc:assets/ $ ls -al
total 676
drwxrwxr-x 2 bongtrop bongtrop 4096 Jan 12 19:04 .
drwxrwxr-x 7 bongtrop bongtrop 4096 Jan 12 19:04 ..
-rw-rw-r-- 1 bongtrop bongtrop 680631 Dec 31 1979 index.android.bundle
(hack) bongtrop@bongtrop-pc:assets/ $ cat index.android.bundle
var __BUNDLE_START_TIME__=this.nativePerformanceNow?na[...]
(3) The JavaScript source code in index.android.bundle
file is minified. You can beautify it by using the js-beautify
tool. It can be installed by executing npm install -g js-beautify
command.
bongtrop@bongtrop-pc:assets/ $ js-beautify -r index.android.bundle
beautified index.android.bundle
(4) After beautifying the JavaScript source code, search Increase button has already been broken
text (line 29156). Then, change the 2 != t.state.counter
condition to 1
.
From:
2 != t.state.counter ? (t.setState({
counter: t.state.counter + 1
}), 9 == t.state.counter && alert("You win !!")) : alert("Increase button has already been broken.")
To:
1 ? (t.setState({
counter: t.state.counter + 1
}), 9 == t.state.counter && alert("You win !!")) : alert("Increase button has already been broken.")
(5) Save index.android.bundle
. Next, navigate to the parent directory and remove all old signatures in the META-INF
directory.
bongtrop@bongtrop-pc:assets/ $ cd ..
bongtrop@bongtrop-pc:ReactNativeReverseingLab/ $ rm META-INF/CERT.RSA
bongtrop@bongtrop-pc:ReactNativeReverseingLab/ $ rm META-INF/CERT.SF
bongtrop@bongtrop-pc:ReactNativeReverseingLab/ $ rm META-INF/MANIFEST.MF
(6) Zip all files into an APK.
bongtrop@bongtrop-pc:ReactNativeReverseingLab/ $ zip -r ReactNativeReverseingLab.patch.apk *
adding: AndroidManifest.xml (deflated 66%)
adding: assets/ (stored 0%)
adding: assets/index.android.bundle (deflated 81%)
[...]
bongtrop@bongtrop-pc:ReactNativeReverseingLab/ $ mv ReactNativeReverseingLab.patch.apk ..
bongtrop@bongtrop-pc:ReactNativeReverseingLab/ $ cd ..
(7) Sign the APK.
If you don’t have a key, generate it with the following command.
keytool -genkey -v -keystore helloworld.keystore -alias helloworld -keyalg RSA -keysize 2048 -validity 10000
Then, sign the APK with apksigner
or jarsigner
.
bongtrop@bongtrop-pc:ReactNativeReverseingLab/ $ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore helloworld.keystore ReactNativeReverseingLab.patch.apk helloworld
Enter Passphrase for keystore:
adding: META-INF/MANIFEST.MF
adding: META-INF/HELLOWOR.SF
adding: META-INF/HELLOWOR.RSA
[...]
(8) Finally, install the new APK and tap on + button. The You win!!
alert will be pop up as shown below.
In a nutshell, without Hermes, you can beautify the JavaScript source code then easily analyze and patch it. As a result, I really love to do reverse engineering on React Native without Hermes. We can also inject a JavaScript debugging tool into the application and debug it with the browser’s dev tools.
React Native with Hermes
We are finally reaching the main point. To enable the Hermes engine for React Native application, you can easily edit the android/app/build.gradle
file to change enableHermes: false
to enableHermes: true
.
For more information, please visit: https://reactnative.dev/docs/hermes
After building the application, the index.android.bundle
in Hermes bytecode format will be created. In Ubuntu 20.04, the file
command can identify this format.
$ file index.android.bundle
index.android.bundle: Hermes JavaScript bytecode, version 74
If you want to perform the reverse engineering on the Hermes bytecode by yourself, you can test in our lab (Hermes Reversing Lab).
By investigating the index.android.bundle
file, you will discover that it cannot be read. If it is .NET or Java bytecodes, you can use the decompiler tool from the internet to decompile it. But, for a new engine like Hermes, there is no hope.
A built-in tool like hbcdump
can help you analyze the application. You can download it from Hermes Github repo. Please note that you must use the correct version of Hermes bytecode to disassemble the application. The version of Hermes bytecode can be found in /include/hermes/BCGen/HBC/BytecodeFileFormat.h
file around line 33. For the Hermes Reversing Lab, it was built with Hermes bytecode version 74. Please use hbcdump
from Hermes Release v0.6.0. After downloading, execute it as follows:
$ ./hbcdump -objdump-disassemble index.android.bundle
hbcdump> dis
d0310a88a868dfb1ee21d12e9011725b1f716875: file format HBC-74
Disassembly of section .text:
000000000002ca48 <_0>:
0002ca48: 30 44 08 00 00 DeclareGlobalVar $0x000844
0002ca4d: 30 48 08 00 00 DeclareGlobalVar $0x000848
[...]
hbcdump> quit
Then, if you want to patch it, use your favorite hex editor tool to modify the index.android.bundle
file directly.
It can be seen that it is still hard even when the Hermes team provides the hbcdump
for us. The hbcdump
doesn’t show all the important information in the assembly. And to patch the application, editing raw bytes with a hex editor is still required.
Therefore, I created a tool named hbctool to help analyze and patch Hermes bytecode from React Native application. To install it, execute the following command.
pip install hbctool
Let’s do it together. After downloading and installing the lab’s APK from Hermes Reversing Lab, open it and tap the + button 10 times. The Increase button has already been broken.
message will be shown and counter
will not increase anymore.
To get a flag, we have to increase the counter to 1337. Thus, the Hermes bytecode must be patched to bypass the condition checking, or you can also perform reverse engineering on the encrypt function and reconstruct the decrypt function in order to get the flag. In the following steps, I will only show you the Hermes bytecode patching method.
(1) Unzip the APK file (you can also use apktool
in this step).
(hack) bongtrop@bongtrop-pc:lab/ $ unzip HermesReversingLab.apk -d HermesReversingLab
Archive: HermesReversingLab.apk
inflating: HermesReversingLab/AndroidManifest.xml
inflating: HermesReversingLab/META-INF/CERT.RSA
inflating: HermesReversingLab/META-INF/CERT.SF
inflating: HermesReversingLab/META-INF/MANIFEST.MF
[...]
(2) Disassemble Hermes bytecode from index.android.bundle
file by using hbctool
.
(hack) bongtrop@bongtrop-pc:lab/ $ hbctool disasm HermesReversingLab/assets/index.android.bundle HermesReversingLabHASM
[*] Disassemble 'HermesReversingLab/assets/index.android.bundle' to 'HermesReversingLabHASM' path
[*] Hermes Bytecode [ Source Hash: d0310a88a868dfb1ee21d12e9011725b1f716875, HBC Version: 74 ]
[*] Done
(3) After disassembling HBC (Hermes Bytecode) to HASM (I named it; stands for Hermes Assembly). In the HermesReversingLabHASM
directory, there are 3 files as follows:
metadata.json
: stores the important information of Hermes bytecode fileinstruction.hasm
: stores the application instructions or logics in HASM format (edit application logics in this file)string.json
: store the application strings or texts (edit strings in this file)
(4) Edit the application’s instruction in HermesReversingLabHASM/instruction.hasm
. You can change the goal counter from 1336 to 1, or you can just change the opcode from JNotGreaterEqual
to another jmp
opcode. For this example, I would change the goal counter from 1336 to 1 by changing the number 1336 on line 182890 to 1.
From:
[...]
LoadConstInt Reg8:1, Imm32:1336
JNotGreaterEqual Addr8:43, Reg8:2, Reg8:1
[...]
To:
[...]
LoadConstInt Reg8:1, Imm32:1
JNotGreaterEqual Addr8:43, Reg8:2, Reg8:1
[...]
(5) Save the file and assemble HASM to the HBC by using hbctool
.
(hack) bongtrop@bongtrop-pc:lab/ $ hbctool asm HermesReversingLabHASM HermesReversingLab/assets/index.android.bundle
[*] Assemble 'HermesReversingLabHASM' to 'HermesReversingLab/assets/index.android.bundle' path
[*] Hermes Bytecode [ Source Hash: d0310a88a868dfb1ee21d12e9011725b1f716875, HBC Version: 74 ]
[*] Done
(6) Remove all signature files from META-INF
directory and zip them to APK.
(hack) bongtrop@bongtrop-pc:lab/ $ cd HermesReversingLab
(hack) bongtrop@bongtrop-pc:HermesReversingLab/ $ rm META-INF/CERT.RSA
(hack) bongtrop@bongtrop-pc:HermesReversingLab/ $ rm META-INF/CERT.SF
(hack) bongtrop@bongtrop-pc:HermesReversingLab/ $ rm META-INF/MANIFEST.MF
(hack) bongtrop@bongtrop-pc:HermesReversingLab/ $ zip -r HermesReversingLab.patch.apk *
adding: AndroidManifest.xml (deflated 66%)
adding: assets/ (stored 0%)
adding: assets/index.android.bundle (deflated 48%)
[...]
(hack) bongtrop@bongtrop-pc:HermesReversingLab/ $ mv HermesReversingLab.patch.apk ..
(hack) bongtrop@bongtrop-pc:HermesReversingLab/ $ cd ..
(7) Sign the APK file by using jarsigner
or apksigner
.
(hack) bongtrop@bongtrop-pc:lab/ $ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore helloworld.keystore HermesReversingLab.patch.apk helloworld
Enter Passphrase for keystore:
adding: META-INF/MANIFEST.MF
adding: META-INF/HELLOWOR.SF
adding: META-INF/HELLOWOR.RSA
[...]
(8) Finally, after installing and opening the patched app, tap the + button twice. The flag will be shown. Please note that the example in the following gif file uses the apkutil to bundle and unbundle the APK file.
For a higher quality video, see this MP4.
I created this lab for anyone that wants to try reverse-engineering the Hermes bytecode application. If you get a flag, you can submit it at lab.suam.wtf.
Summary
I wrote this blog because I want more people to be interested in doing an in-depth analysis of the mobile application framework. Researching the internals of any framework takes a huge time. I cannot research all frameworks by myself. Please research them and share your knowledge with the community, to help a poor pentester like me.
However, I hope that my tool can help pentester with bad times on mobile application pentest. The days are getting worse.
From the technical point of view, the Hermes engine is very interesting. There is no disadvantage to enabling it (except making it hard to perform reverse engineering), and it makes the application run faster. In the future, I think if the Hermes engine is stable, the React Native applications will be forced to migrate to it. Moreover, in my opinion, AOT for React Native is coming soon.