halis duraki/
- 0xduraki
The idea of this note is to describe technique and process which might allow reverse engineers to enable debugging capabilities in third-party apps. and force this “development” context without any limitations, eventually allowing us to debug applications straight out of the Google PlayStore or the APK distributions.
For a quick start, it’s recommended to go through Developing simple app in React Native and Exploring React Native Apps on Android notes to get a feel of React Native and its’ ecosystem. Additonally, base notes were described in Using APKLab to Enable DevMode in React Apps which provides setup instruction for APKLab Workbench, and patch modifications on React apps. Java native level.
We will use our Demo App and build the release-variant APK to study and anlyze this topic.
Build the release variant of the Android demo application using:
$ npm run build
# > rnprojectdemo@0.0.1 build
# > react-native run-android --mode=release
#
# info A dev server is already running for this project on port 8081.
# info Installing the app...
#
# > Task :app:createBundleReleaseJsAndAssets
# Writing bundle output to:
# rnprojectdemo/android/app/build/generated/assets/createBundleReleaseJsAndAssets/index.android.bundle
# Writing sourcemap output to:
# rnprojectdemo/android/app/build/intermediates/sourcemaps/react/release/index.android.bundle.packager.map
#
# > Task :app:installRelease
# Installing APK 'app-release.apk, app-release.dm' on 'Nexus 10 - 13' for :app:release
# Installed on 1 device.
#
# Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.rnprojectdemo/.MainActivity }
The APK distribution files should be generated in: android/app/build/outputs/apk/
in (debug)
and (release)
subdirectories:
$ ls -la [project]/android/app/build/outputs/apk/
# drwxr-xr-x 4 xxxxxxx staff 128 Feb 9 19:37 debug # -> app-debug.apk
# drwxr-xr-x 5 xxxxxxx staff 160 Feb 9 22:11 release # -> app-release.apk
If you install the release version of the APK and try to get into “Developer Menu” using CMD+Shift+M
keyboard shortcut, or by sending the keycode 82
over adb
, nothing will happen since App. Devmode is disabled on “release” distributions.
Decompile the Releases-version APK Follow the notes in Exploring React Native Apps on Android to decompile and explore the built “Releases” APK distribution.
DEV Components in APK
We can try forcing the DEV
flag in the Javascript Bundle (assets/index.android.bundle
) file and repack (recompile) app again with single modification; that is, replacing the __DEV__=false
instruction early in the bundle to __DEV__=true
. Once the app is recompiled and installed on the device, nothing happens. There were no requests observed to port 8081
, Dev Menu can’t be triggered, however, manually opening the Dev Settings presented the “JS Dev Mode” checkbox enabled:
$ adb shell "am start com.rnprojectdemo/...DevSettingsActivity"
# ...
Other than that, no other way was found via above method to open apps’ dev-tools, nor a way to debug the app.
Searching around, I’ve found Leo’s and EdgeApp’s notes very valuable. They indicate that there are 2x components that should have DEV
state:
- The JavaScript code
- The Native (Java) code
The above process only switched the JavaScript counterpart to “DEV”, but was missing native Java context to allow DEV stuff. The native code responsible for all the DEV stuff is part of the react-native
core module, and lies in thhe com.facebook.react.devsupport
package.
Esentially, the critical bits to investigate are under demo apps’ directory node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/*
In detail, the decision to include this package’s functionality or not, is located in the DevSupportManagerFactory
class:
// node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java
package com.facebook.react.devsupport;
// ...
public interface DevSupportManagerFactory {
/**
* Factory used by the Old Architecture flow to create a {@link DevSupportManager} and a {@link
* com.facebook.react.runtime.BridgeDevSupportManager}
*/
DevSupportManager create(..., boolean enableOnCreate) {
}
The factory class responsible for deciding whether to enable Developer Menu in the app is in Kotlin’s DefaultDevSupportManagerFactory
class:
// node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DefaultDevSupportManagerFactory.kt
package com.facebook.react.devsupport
// ...
public class DefaultDevSupportManagerFactory : DevSupportManagerFactory {
// ...
public override fun create(/* ... */, enableOnCreate: Boolean, /* ... */): DevSupportManager {
return if (!enableOnCreate) {
ReleaseDevSupportManager()
} else
try {
// Developer support is enabled, we now must choose whether to return a DevSupportManager,
// ...
The enableOnCreate
argument is traced back to abstract class ReactNativeHost
, which by the way React Native works, is instantiated by demo app. own’s Kotlin class MainApplication.kt
:
// android/app/src/main/java/com/rnprojectdemo/MainApplication.kt
package com.rnprojectdemo
// ...
class MainApplication : Application(), ReactApplication {
override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
// ...
//
// This function returns whatever is in 'BuildConfig.DEBUG' flag
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
}
// ...
}
In the excerpt above it’s visible that the inclusion of Developer/Debugging support code boils down to the BuildConfig.DEBUG
, replaced by Gradle task on buildtime, therefore, after decompiling, will look like this pseudocode for the above class:
package com.rnprojectdemo
class MainApplication : Application(), ReactApplication {
// ...
fun getUseDeveloperSupport() {
return false;
}
}
To locate the overriden getUseDeveloperSupport()
function we must keep in mind that smali-bytecode displays nested classes with the nesting class’s name, appended with a dollar sign ($
). Therefore, the class we need to modify is:
smali/com/rnprojectdemo/MainApplication$reactNativeHost$1.smali
The smali
bytecode for the aftermentioned method looks similar to this:
# /smali/com/rnprojectdemo/MainApplication$reactNativeHost$1.smali
...
# virtual methods
.method public getUseDeveloperSupport()Z
.locals 1
const/4 v0, 0x0 # replace with 0x1
return v0
.end method
Change the value 0x0
to 0x1
in v0
to patch BuildConfig.DEBUG
value to Bool(True)
, and rebuild the APK, sign it and install it on the device. Once the patched APK is installed, type the following in Terminal, in the rnprojectdemo
repository/directory:
$ npm run react-native start