Tutorial Dexguard, Android and React Native

react native
Friday, August 6, 2021

Tutorial Dexguard, Android and React Native

DexGuard is a specialized optimizer and obfuscator for Android applications. Once applied to your application or library, it produces code that is optimized and more difficult to crack.

Tutorial guide for the basic steps of processing your application with DexGuard in your React Native applications.

  1. Setup your android project.
  2. Setup the security features.
  3. Setup Bitrise workflow.
  4. Build and validate report.

Dexguard Technical aspects

From official documentation.

DexGuard processes Android applications and libraries, making them smaller, more efficient, and better hardened against reverse engineering and tampering.

Optimizing and protecting applications

DexGuard can be used to protect applications that have been produced by the Android build toolchain.

Source code and resources optimized and protected (.apk) with dexguard.

  • Android manifest (text .xml)
  • Java source code (.java)
  • Native source code (.c, .cpp)
  • Resources (text .xml)
  • Resource files (text .xml, .png,...)
  • Assets (.txt,...)

DexGuard reads the input .apk file and optimizes and protects the code and the resources.

DexGuard packages the processed output into an output apk. DexGuard can optionally sign and align the output, removing the need for external tools.

How it works?

DexGuard processes the resources and the code in distinct but seamless steps.

Input: Compiled, unprotected code and resources.

  1. Shrinking: the shrinking step detects and removes unused resources, resource files, native libraries, classes, fields, methods, and attributes.
  2. Optimization: the optimization step analyzes and optimizes the resources and the bytecode of the methods.
  3. Obfuscation Encryption: the obfuscation step renames the remaining resources, resource files, native libraries, classes, fields, and methods using short meaningless names. It obfuscates the bytecode inside specified methods. Finally, it encrypts specified strings, classes, native libraries, resource files, and assets.
  4. Final conversion: the final conversion step translates the Java bytecode (*.class) to Dalvik bytecode (classes.dex).

Output: Protected, secure code and resources.


Setup your android project

Configuring Android Gradle's

Edit android/build.gradle

We recommend to install Dexguard using the maven repository.


// maven {
//    url("$rootDir/../../proguard/DexGuard-9.0.10/lib")
// }

maven {  
	// For the DexGuard Gradle plugin jar.
	credentials  {
 		username = "${DEXGUARD_USERNAME}"
		password = "${DEXGUARD_PASSWORD}"
	}
	url "https://maven.guardsquare.com"
  
	// Only search for artifacts with groupId "com.guardsquare.*",
	// supported since gradle 5.1.
	content {
		includeGroupByRegex "com\\.guardsquare.*"
	}
	authentication {
		basic(BasicAuthentication)
	}
}

Add to the buildscript dependency plugin:


dependencies {
	classpath('com.android.tools.build:gradle:4.1.1')
  // Dexguard dependency
	classpath 'com.guardsquare:dexguard-gradle-plugin:1.1.5'
}

Edit android/gradle.properties and add the variables, you can inject this values from a CD/CI like bitrise.


org.gradle.jvmargs=-Xmx4608m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
android.useAndroidX=true
android.enableJetifier=true
android.enableR8=true
android.enableR8.fullMode=true
 

DEXGUARD_USERNAME=admin@xxx.com
DEXGUARD_PASSWORD=yourpass

Edit your android/app/build.gradle


// Apply Dexguard Plugin
apply plugin: 'com.android.application'
apply plugin: 'dexguard'

// Re-configure your buildtypes, remove the proguardFile configurations
// and set minifyEnabled and shrinkResources to false.
android  {
    buildTypes {
        release {
            minifyEnabled false
            shrinkResources false
        }
    }
}

// Add your dexguard configuration block
dexguard {

	// if you are using dexguard locally
	// path = '../../../proguard/DexGuard-9.0.10'

	version = '9.1.+'
  
  // Keep the license secure in your CD/CI
	license = '../../android/dexguard-license-ANDROID.txt'

	configurations {
		release {
			defaultConfiguration 'dexguard-release.pro'
			configuration 'dexguard-project.txt'
			configuration 'proguard-project.txt'
		}
	}
}

// Add the runtime dependency
dependencies {
	implementation fileTree(dir: "libs", include: ["*.jar"])
	implementation "com.facebook.react:react-native:+"  // From node_modules
  
	implementation 'com.guardsquare:dexguard-runtime:9.1.5'
}

Configure security features

Dexguard security features review

Reflection

DexGuard will replace invocations by reflection and then encrypt the resulting strings. Very difficult to find with static analysis.


# Encrypt the super security class
-encryptstrings class com.antit.io.Security
-accessthroughreflection class com.antit.io.Security {
	boolean passEnvironmentChecks (Context);
	boolean passTamperDetection (Context);
}

Verification: read the verbose logs of building.


Adding reflection...
  Number of reflective class references:         0
  Number of reflective field reads:              73
  Number of reflective field writes:             0
  Number of reflective constructor invocations:  0
  Number of reflective method invocations:       0

File detection, Tamper Detection and Certificate Verification

Anti-tamper technology keeps hackers from modifying your app’s execution to learn its weaknesses and bypassing security features to misuse the software.

Tamper detection actively checks the integrity of the application at runtime and acts suitably if the application has been modified.


boolean isTampered = intToBoolean(TamperDetector.checkApk(context));
boolean isCertificateValid = intToBoolean(CertificateChecker.checkCertificate(context));

Verification: by unpacking the application and repackaging it, with unzip and zip.

zipalign -v -f 4 binary.apk

Use our method passTamperDetection to verify tamper and certificate.

Debugger Detection, Hook Detection, Root Detection, Virtual Environment

Sanity check to detect the environment in which the application is running.


boolean isDebuggable = intToBoolean(DebugDetector.isDebuggable(context));
boolean isDebuggerConnected = intToBoolean(DebugDetector.isDebuggerConnected());
boolean isRooted = intToBoolean(RootDetector.isDeviceRooted(context));
boolean isApplicationHooked = intToBoolean(HookDetector.isApplicationHooked(context));

Use our method passEnvironmentChecks to detect multiple environment checks.

The following checks are performed:

  • HookDetector: Checks if the application is being hooked with a specialized framework like Xposed or Substrate
  • RootDetector: Checks if the application is running on a rooted device.
  • DebugDetector: Checks if the application can be debugged or if it is being debugged.
  • EmulatorDetector: Checks if the application is running on an emulator.

Verification: use a rooted android, APK emulator or try to attach some debugging point with Xposed.

Code Obfuscation

It allows to obfuscate all methods of all classes inside package.


-obfuscatecode,high class com.api.services.**

Verification: decompile the apk and compare the resulting class with the original one, as well the verbose option will print how many clases are obfuscated.

Code Virtualization

Use code virtualization when Class encryption cannot be used due to technical constraints (e.g. Activities referenced by the AndroidManifest) or when it is too heavy to efficiently use as a final protection layer.


// Virtualize propietary method
-virtualizecode class com.api.Generator {
    public java.lang.String[] generateSID();
}

Verification: decompile the apk and compare the resulting class with the original one, as well the verbose option will print how many clases are obfuscated.

Native Library Encryption

DexGuard obfuscates JNI functions inside native libraries. This technique prevents static analysis of the native code.


-encryptnativelibraries lib/*/onfido.so

Verification: unpack encrypted native library and visualize the encrypted content opposite to the ELF format.

Asset Encryption

Dexguard can encrypt specific asset files.


-encryptassetfiles assets/license.txt

Verification: unpack encrypted binary and visualize the encrypted content opposite to the original format.

Resource Encryption and Resource File obfuscation

DexGuard encrypts only inlines resource strings but it posible to explicitly specify to encrypt sensitive resource strings.


-encryptresources string/title

Verification: Use appt to dump the strings in the hardened binary.

aapt d --values strings [APK_FILE]

Metadata Encryption

DexGuard encrypts only inlines metadata in the Android Manifest but it posible to explicitly specify encrypt other metadata values.


-encryptmetadata mySecretKey

Verification: unpack and explore the Android Manifest in the hardened release version.


Class Encryption

Name obfuscation, string encryption, reflection, asset encryption, resource encryption and native library encryption harden the code and resources against static analysis.


-encryptclasses com.api.Service,
                com.api.Service$*

Specially usefully to add extra security layer to the reflection and other secure techniques applied to our binary.

Verification: hook a disassembler to the hardened version to find traces of string encryption, reflection, tamper detection and environment checks, them compare after adding class encryption in some classes, they won't longer be visible.

Name Obfuscation

DexGuard obfuscates the names of identifiers like resource files, resources, classes, fields, and methods using meaningless names. This is a default behavior so there is no code to apply.

Verification: use dexdump or baksmali to disassemble the code and explore the names.

String Encryption

DexGuard can encrypt sensitive string constants, so they become invisible to static analysis. If you have string obfuscation applied they can be hidden completely with string encryption.


-encryptstrings class com.class.SecretClass

Verification: DexGuard will write the list of strings it encrypted, grouped per class, to a file.

Javascript obfuscation

DexGuard can obfuscate JavaScript files using DexGuard's built-in JavaScript obfuscator. Specify a Javascript configuration with the option configuration.yml.


processing:

  assumptions:
    # You can define a whitelist of debug variables which will always be false
    # during execution. With shrinking enabled, these variables will then be
    # optimized away.
    false-variables:
      whitelist:
        - DEBUG
    no-side-effect-methods:
      whitelist:
        - console.log

  shrinking:
    # The obfuscator is not always able to detect references to global
    # variables or properties. You can leave them disabled, or add an extra
    # blacklist that excludes problematic globals/properties.
    # globals:
    # properties:
    code:

  protection:
    data:
      strings:
        # Encryption of strings may introduce an excessive overhead for certain
        # performance-critical functions. You can define a whitelist (to
        # encrypt specific sensitive strings) or a structured blacklist (to
        # encrypt all strings, except strings in performance-critical
        # functions).
        # encrypt:
        arrayize:
    code:
      names:
      # The obfuscator is not always able to detect references to global
      # variables or properties. You can leave them disabled, or add an extra
      # blacklist that excludes problematic globals/properties.
      # globals:
      # properties:
      property-access:
      # Encryption of property access obfuscation may introduce an excessive
      # overhead for certain performance-critical functions. You can leave it
      # disabled or enable it but add an extra structured blacklist to
      # exclude performance-critical functions.
      # encrypt:
      control-flow:
      numbers:
      arithmetic-operations:
      shuffle:
    environment-integrity:
      debugger:
        obstruction:
        # Encryption of debugger obstruction may introduce an excessive overhead
        # for certain performance-critical functions. You can leave it disabled
        # or enable it but add an extra structured blacklist filter to exclude
        # performance-critical functions.
        # encrypt:

# Use the prettify option to control whether the written JavaScript files should be
# formatted with proper indentation. Only use this for debugging.
# output:
#   prettify: true

Verification: unpack the binary and open the javascript files.

Setup BITRISE workflow

1. Configure your credentials as secret variables

Configure your dexguard username and password as secret variables

and protect the secret

2. Configure your dexguard license

Upload the dexguard license into the GENERIC STORAGE service and create a download step to import the file in the bitrise VM build time.

generic storage bitrise and dexguard
file downloader step for dexguard license

3. Configure the Maven credentials

Create a new script step in your workflow before the build step, this will append the creds to the gradle properties.


echo "\nDEXGUARD_PASSWORD=$DEXGUARD_PASSWORD" >> /Users/vagrant/git/android/gradle.properties

4. Configure Artifacts step to export the Dexguard report

Configure a new Deploy artifacts step to export the generated security report on each new build.

Build and iterate

We recommend to test your obfuscated application's functionality with a QA Test Suite, having diferente test plan for each security feature, from this point you can have a full secure continuous integration and deployment flow.

Verify

  • dexdump (Android SDK): disassembles Dalvik bytecode to a readable text format.
  • aapt (Android SDK): disassembles binary resource XML files to a readable text format.
  • baksmali (open source): disassembles Dalvik bytecode to a readable source format.
  • smali (open source): assembles this source format to Dalvik bytecode again.
  • apktool (open source): disassembles and assembles entire applications: bytecode, Android manifest files, resource files, and assets.
  • dex2jar (open source): converts Dalvik byte code to Java bytecode.
  • jad (free): decompiles Java bytecode to Java source code.

Hopefully this guide may be good to you.

james jara
August 6, 2021