This checklist provides a single place to review the things to consider when protecting your app or library with PreEmptive Protection™ DashO™. This checklist should be reviewed from time to time and each time you update to new versions of DashO.
Development Practices
Although not required, we assume that you have:
- Source control system
- Build artifact repository (capable of storing publishable and private build artifacts)
- Continuous integration environment distinct from developer or integrator systems
Source Files
The following files should be treated as source files and should be maintained in your source control system:
- The project configuration file (
some-project.dox
) - Any files generated by the DashO Project Wizard, if used (
.gitignore
,gradle.properties
, Ant files supporting.war
projects, etc.) - Only when using Incremental Obfuscation: the Renaming Map File and the String Encryption Map File as inputs (
some-project-input.map
andsome-project-input-se.map
, Standard Mode only)
These files will need to evolve with the software.
Build Artifacts
The following files should be treated as private build artifacts (or could be gathered in an archive file that would then be treated as a private build artifact):
- Renaming Map file (
some-project.map
for Standard Mode, orbuild/.../mapping.txt
output by R8 for Android Mode projects) - String encryption map file (
some-project-se.map
for Standard Mode only) - A log of the DashO console output (this will be helpful when reviewing build warnings or errors, automatically recorded for Android Mode projects under
build/outputs/logs
) -
DashO reports (
some-project-report.txt
andsome-project-mapreport.txt
for Standard Mode only)
Some of these files are important for maintaining a record of what protections were applied and should be available for future reference (auditing, etc.). Renaming Map files can be used to decode stack traces from protected applications. Renaming Map files and the Renaming report can be used to effectively undo Renaming, and thus must remain secret.
Depending on the sensitivity of the project it may be necessary to treat these files differently. For example, the build log might be published internally and used to verify protection, but the Renaming Map file and Renaming report might only be accessible to certain personnel because of the possibility of using them to undo protection.
If unprotected application or library binaries are preserved, they would need to remain private. All of the files comprising the protected application or library binaries would be publishable build artifacts.
It is important that each build has a distinct version, so that there is no confusion between which binary corresponds to which e.g. log file or Renaming Map. For example, map files can be very different between builds (not using incremental obfuscation), and using the wrong map file to decode a stack trace will produce incorrect results.
Temporary Files
Files that do not need to be preserved include:
- DashO automatic input management cache files (
.dasho/
Android Mode only) - Any DashO-related temporary or intermediate files (including those under
build/tmp
orbuild/intermediates
for Gradle-builds)
Build Integration
The continuous integration build should include DashO protection as a step. In doing so, you should verify the following:
-
The build is clean. Any existing binaries or intermediate files are removed before the build to ensure that the result of prior builds are not accidentally used.
-
The product is built in release mode.
For Standard Mode projects, this may be as simple as not compiling with the
-g
option tojavac
. DashO by default will strip out debugging information, but this will avoid putting in debugging information to begin with.For Android Mode projects, this means building the release build type. If nothing else, this will ensure that DashO will be applied to the app with release signing.
-
The build server and development machines have the latest version of DashO available installed. Or at least the latest version to which you are entitled based on your license. We are always improving our protection.
-
The build server is using a build license. Only DashO build licenses may be used when protecting software for general release. This can be verified by reviewing the command-line output.
-
DashO must run and complete without error. The build should fail if the DashO protection step terminates with a non-zero exit code. This will happen automatically for Android Mode projects. The build process should preserve the DashO build output either in the general build log files, or as a separate build log. Consider enabling verbose output.
-
Make sure to monitor your builds for warnings emitted by DashO. Generally, DashO warnings should be addressed by adjusting configuration or modifying code. There are a handful of warnings that cannot be worked around (for example the warning emitted when provisional module support is enabled). It might be useful to maintain a list of these to ensure that new warnings are not ignored. New versions of DashO may add warnings for conditions that might indicate previously unknown problems in existing projects. New warnings may be emitted if changes are made to the software without corresponding updates to the DashO configuration.
-
Ensure that signing was done as expected. DashO Tamper Checks work with app signing (Android Mode, and Standard Mode APK builds) and jar signing (other Standard Mode projects). The certificate specified in the DashO configuration must match that used to sign the binaries that the end-user will receive. This should be straightforward for non-Android projects, but refer to Android Signing for details on how to do this for Android projects.
-
When DashO runs, it may send feature usage and summary project data to our servers through the Customer Feedback Program. This is enabled by default. If you and your company can, please leave this feature enabled. This helps guide our development of new and existing features.
Product Configuration
It is important that you adjust your DashO configuration appropriately for your project.
-
If you are building an Android project, do each of the things described in Enhance Protection for Android.
-
If you are building a non-Android project, make sure to enable the major features: Renaming, Control Flow, String Encryption, and Removal. Also add Tamper Checks, preferably using Tamper Responses to dissociate the Check from the action. This protects the code from being changed. Also add Debugging Checks, preferably using Debugging Responses to dissociate the Check from the action. This protects the code from being examined, which is the first step to removing Checks and altering the code. In this way, the Checks and obfuscation features work together to protect your code.
-
If your project is a library, make sure that the API has been correctly preserved. Public classes and methods in the API are marked as Entry Points. You should have a test app that exercises all of the features of your library, including for example String constants, etc. It should be possible to both compile and run this test app with your protected library.
-
If you use String Encryption, it is important to also enable Removal. See String Encryption for details.
Verifying Protection
It is also important to verify that the protection options that you have deployed have done the job that you expect. Use tools on your local machine that you trust (e.g. no remote services) to verify protection.
For Android projects, you can use unzip
, Apktool (under DashO tools/
), and dex2jar (under DashO tools/
) to examine your .apk
or .aar
. For non-Android projects, you can use unzip
, or jar
to examine your .jar
s or .war
. Once you have the class files, you can use javap
or even strings
to examine aspects of your class files. It is also a good idea to examine your class files using your decompiler of choice.
-
Renaming: Unzipping the protected binary, you should be able to see that most classes are renamed. Within class files you should be able to see that most methods and fields have been renamed. If there are methods with particularly sensitive names, verify that they have been renamed. These names should be consistent with what appears in the Renaming Map files, but verifying the binary directly is the best way to be certain.
For Android projects, use
unzip
or Apktool to get theclasses.dex
files, then usedex2jar
to convert these to.jar
files, and then useunzip
orjar
to unzip the converted.jar
files. -
Control Flow: Best checked by examining class files in a decompiler. A decompiling tool called BytecodeViewer will let you try several decompilers at once, for example FernFlower, Procyon, and CFR. If they show stack traces (they "crashed" trying to decompile) or just seemingly uninterpretable garbage code, Control Flow was successful.
The approach of Control Flow obfuscation is to alter the code that is present. Thus don't expect Control Flow to be terribly effective on very small methods (e.g. ones with only a few bytecode instructions). Small methods usually don't offer much to work with, but there wouldn't be much intellectual property to protect in any case.
-
String Encryption: Looking at class files with
javap
should clearly show that the strings have been encrypted. For particularly sensitive strings, use a tool likegrep
to verify that the string does not appear in any of the class files. Verifying this can be important because of the way the Java compiler will inline static final constant Strings. Enabling Removal will ensure that static final String fields are removed. -
Resource Encryption: Open up the
.apk
file withunzip
or your preferred archive tool. For most file formats, e.g. JPEG, MPEG, etc., it should not be possible to open any of the protected files with the normal applications. Moreover, opening the files in e.g. a text editor should reveal a sequence of seemingly random bytes. If there are questions about why particular files are encrypted and others are not, the "Preview" and "Preview All" features in the GUI can be used to better understand how patterns (e.g. regular expressions) are matching filenames. -
Checks can be verified by running the protected app under various conditions:
- Tamper: Sign the protected binary with the wrong certificate
- Debugging: Run the protected app in a debugger
- Emulator: Run the protected app in an emulator
- Root: Run the protected app on a rooted device (or in an emulator)
- Hook: Run the protected app on a device with a hooking framework set up
- Shelf Life: Run the protected app after adjusting system time (which may also require disconnecting from the network, depending on the device)
-
Removal: See that fewer methods / classes exist in the protected binary. Refer to the report to see what was removed and verify that the classes, methods, or fields are not present in the protected project binary.
-
Premark (Watermarking): Verify that the watermark is present with
premark
.
Testing the Protected Product
Although some testing may need to be done on the unprotected application, it is very important that the protected application goes through functional testing, each time, before release. Randomness is inherent in our obfuscation algorithms, and the output is different each time the app is protected. Although we would expect it to be very rare, it is possible that a bug could cause two subsequent protections of the same app to behave differently.
Periodic performance testing of a protected app is a good idea. This ensures that changes to the app have not started to cause a performance problem that was not present when protection for the app was initially configured. It is a good idea to do performance testing when significant changes are made to a project's configuration.
The following sections describe the major features and discuss considerations with respect to functionality and performance as appropriate to each feature.
Renaming (Functional)
Renaming obfuscation is the most common form of obfuscation, and it is highly effective, but it has the potential to break functionality of an application if a symbol is renamed and a reference to that symbol isn't. In most cases, DashO can automatically identify name-based references and automatically rename the reference, such as in simple reflection, or manifest files. But more complex references cannot be statically analyzed, and therefore must not be renamed. DashO attempts to identify such references and automatically exclude appropriate symbol from Renaming, but it is impossible to do this in all scenarios. Because of this, it is important to plan time in the project schedule for functional testing after Renaming is applied. It is also important to decide how aggressively to rename symbols. By default, DashO uses relatively safe settings.
To increase the protection provided by Renaming, the first step is make sure that you specify Entry Points. If entry points are not specified, DashO will treat the project like a library and will not rename any public methods, etc.
You can also change the alphabet and length of the names used.
If your application uses reflection in a relatively straightforward way, it may make sense to also rename reflected classes.
Control Flow (Performance)
In most cases Control Flow obfuscation is low-risk. DashO currently offers three types of Control Flow obfuscation: Block Jumbling, Try/Catch obfuscation, and Block Splitting. All three are on by default.
In performance-sensitive areas of the code, though, Control Flow obfuscation can degrade performance, so if that is a concern then performance should be tested and/or performance-sensitive areas of the code should be excluded from Control Flow obfuscation.
Some Control Flow obfuscation may not behave correctly on Dalvik Android devices (Android KitKat and earlier). If you intend for your Android app to run on Dalvik devices, you should consider enabling Dalvik compatibility.
String Encryption (Performance)
String Encryption obfuscation is on by default with relatively safe settings. String Encryption changes how strings are accessed, so it has an effect on performance, especially when strings are accessed in tight loops. Application performance should be tested to identify any performance changes caused by String Encryption.
Because of the way the Java compiler inlines uses of string constants, it is important to enable Removal of unused fields when using String Encryption.
For improved protection at the cost of decreased performance, the String Encryption "level" can be turned up. The default value of 2
provides a safe balance between protection and speed.
For particularly sensitive strings, Custom String Encryption may be used. This allows you to provide your own jar for encrypting and decrypting strings. Custom String Encryption is applied nowhere by default, whereas String Encryption is applied everywhere by default. Take care when using Custom String Encryption that not only the classes and methods where the string constants are defined are included, but also all of the classes and methods where the string constants are referenced.
Resource Encryption (Functional, Performance)
Resource Encryption allows Android assets and raw resources to be encrypted. The DashO Gradle Plugin for Android adds a step in the Gradle build process to encrypt the resources, and this feature is therefore not available to Standard Mode APK projects. Resource Encryption is off by default. To use it, enable it and configure which assets and raw resources should be encrypted.
DashO must wrap calls that access resources with code to decrypt the resources before they're used. Android resources can be accessed in numerous ways. DashO supports common modes of access, but does not support all of them. It is therefore important to do functional testing on an application after Resource Encryption has been applied to ensure that DashO was able to wrap all of the calls.
Decryption of the assets and raw resources at run-time does require a non-negligible amount of processing power. It is therefore important to also do performance testing of the app after enabling Resource Encryption, particularly on devices you support that have slower CPUs or a lower core count. Depending on the amount and size of resources in your application you may need to exclude less sensitive assets.
Checks (Functional)
All Checks in DashO are executed at specific times and places in your application's lifecycle, specifically wherever you configure DashO to inject them. It is typically appropriate to inject Checks at or near application startup, as well as in other places throughout the app lifecycle, especially around sensitive areas of the code.
All Checks in DashO share a common set of behaviors that can be configured when each Check is triggered. Each of those behaviors requires some forethought.
First, Checks can call methods (or set fields) in the application to enable custom behavior when triggered. Custom behavior is used to change application behavior and/or to use third-party analytics platforms. To use custom behavior, application code will have to be modified and/or added. Therefore, this is only an option if developers are available to change the code.
Second, DashO can inject code that automatically performs pre-defined actions (e.g. exiting the app). An important consideration with any Check-triggered behavior (built-in or custom) is the appropriateness of the behavior. For example, if a Debugging Check is triggered, it might be appropriate to exit the app, or to limit the application's feature set. It is probably not appropriate to wipe all the application data, though, as there are potential legitimate scenarios where a debugger could be attached to a production application.
Unused Code Removal (Functional)
DashO can also remove unused code from your application. This only applies to Standard Mode projects. In Android Mode projects, R8 is responsible for removal. This helps reduce binary size and reduces the attack surface of the application. Removal works automatically by identifying the entry points of the application and statically analyzing all the code that is reachable from those points. Static analysis cannot always find all used code, however, because of e.g. dynamic reflection. Because of this, Removal has the potential to break application functionality. It is important to plan time in the project schedule for functional testing after Removal is applied. It is also important to decide how aggressively to apply Removal.
As with Renaming, if entry points are not specified, DashO will treat the project like a library, preventing removal of public code elements.
DashO has options that allow for increasing or decreasing the aggressiveness of Removal, at the expense of increased risk of functional issues. It is important to confirm that the settings are what you want them to be.
Merge Inputs (Functional)
Merge Inputs is a way to combine multiple inputs into a single output. This can simplify deployment scenarios and make it somewhat harder for an attacker to understand the structure of the application. Merge Inputs defaults to off and must be enabled to be used.
For Android Mode and Standard Mode APK projects Merging Inputs is not really an option since those projects always produce a single output.
Watermarking
Watermarking is a way of embedding a custom string into an application's structure, such that it can't be easily spotted by an attacker. The watermark can be extracted from the application to identify a particular build. This is not available for Android Mode or Standard Mode APK projects.
Release Preparation
- Ensure the steps in the checklist have been followed, or that the reasons why a step or feature does not apply is known and recorded.
- Ensure that the protected build has passed functional testing.
- Make sure that the source files and build artifacts related to protection are preserved appropriately.
Using the approaches described above verifies that the protected application or library is protected as expected.