Reversing DexGuard: Part 1, Part 2, Part 3
The second part of this series on DexGuard focuses on encryption:
- Asset encryption
- Class encryption
- Full application encryption
Those analyses were done statically using JEB 3.21.
Asset Encryption
DexGuard can encrypt assets, while combining other techniques, such as class encryption (seen in several high-profile apps), and bytecode obfuscation (control-flow obfuscation, string encryption, reflected API access). With most bytecode obfuscation being automatically cleaned up, Assets are being accessed in the following way:
The DecryptorFilterStream
object implements a variant of TEA (Tiny Encryption Algorithm), known for its simplicity of implementation and great performance 1.
It seems reasonable to assume that the encryption and decryption algorithms may not always be the same as this one. DexGuard making extensive use of polymorphism throughout its protection layers, it could be the case that during the protection phase, the encryption primitive is either user-selected or selected semi-randomly.
JEB can automatically emulate throughout this code and extract assets, and in fact, this is how encrypted classes, described in the next section, were extracted for analysis. However, this functionality is not present in current JEB Release builds. Since the vast majority of DexGuard uses are legitimate, we thought that shipping one-click auto-decryptors for data and code at this time was unnecessary, and would jeopardize the app security of several high-profile vendors.
Class Encryption
Class encryption, as seen in multiple recent apps as well, works as follows:
- The class to be protected,
CP
, is encrypted, compressed, and stored in a file within the app folder. (The filename is random and seems to be terminated by a dot, although that could easily change.) Once decrypted, the file is a JAR containing a DEX holding CP and related classes. - CP is managed by a custom ClassLoader,
CL
. - CL is also encrypted, compressed, and stored in a file within the app folder. Once decrypted, the file is a JAR containing a DEX holding the custom class loader CL.
- Within the application, code using CP (that is, any client that loads CP, invokes CP methods, or accesses CP fields) is replaced by code using
CM
, a class manager responsible for extracting CP and CL, and loading CL. CM offers bridge methods to the clients of CP, in order to achieve the original functionality.
The following diagram summarizes this mechanism:
Since applications protected with DexGuard use its extensive RASP (Runtime Application Self-Protection) facility to validate the environment they’re running on, the dynamic retrieval of CL and CP may prove difficult. In this analysis, it was retrieved statically by JEB.
Below, some client code using CM to create an encrypted-class object CP and execute a method on it. Everything is done via reflection. Items were renamed for enhanced clarity.
CM is a heavy class, highly obfuscated. The first step in understanding it is to:
- identify and rename the guard0/guard1 fields to allow opaque constructs obfuscating the flow to be optimized away
- disable rendering of catch-blocks that clutter the view.
With auto-decryption and auto-unreflection enabled, the result is quite readable. A few snippets follow:
Once retrieved, those additional files can easily be “added” to the current DEX unit with IDexUnit.addDex()
of your JEB project. Switch to the Terminal fragment, activate the Python interpreter (use py
), and issue a command like:
The protected class CP and other related classes, stored in “/f.” contained… anti-tampering verification code, which is part of DexGuard’s RASP facility! In other instances that were looked at, the protected classes contained: encrypted assets manager, custom code, API key maps, more RASP code, etc.
Full Application Encryption
“Full” encryption is taking class encryption to the extreme by encrypting almost all classes of an application. A custom Application
object is generated, which simply overloads attachBaseContext()
. On execution, the encrypted class manager will be called to decrypt and load the “original” application (all DexGuard protections still apply).
Note that activities can be encrypted as well. In the above case, the main activity is part of the encrypted jar.
Conclusion
That’s it for part 2. We focused on the encryption features of DexGuard. Both offer relatively limited protection for reverse-engineers willing to go the extra mile to retrieve original assets and bytecodes.
In Part 3, we will present what I think is the most interesting feature of DexGuard: code virtualization.
Until next time!
—